diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1015b96..0b2e99d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,7 @@ name: Bug Report description: File a bug report labels: ["bug"] -assignees: +assignees: ["rebelinux"] body: - type: textarea id: bug-description diff --git a/.github/ISSUE_TEMPLATE/change_request.yml b/.github/ISSUE_TEMPLATE/change_request.yml index 8a2cee0..604e378 100644 --- a/.github/ISSUE_TEMPLATE/change_request.yml +++ b/.github/ISSUE_TEMPLATE/change_request.yml @@ -1,7 +1,7 @@ name: Change Request description: Request a new change or an improvement labels: ["change request"] -assignees: +assignees: ["rebelinux"] body: - type: textarea id: description diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 46c2838..89652ea 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -49,29 +49,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/AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 b/AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 index e24a408..e809b78 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.2.0' + ModuleVersion = '0.3.0' # 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' + FunctionsToExport = 'New-PieChart', 'New-BarChart', 'New-StackedBarChart', 'New-SignalChart' # 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 = '*' @@ -127,3 +127,4 @@ + diff --git a/AsBuiltReport.Chart/AsBuiltReport.Chart.psm1 b/AsBuiltReport.Chart/AsBuiltReport.Chart.psm1 index 57d1e66..1aa45ab 100644 --- a/AsBuiltReport.Chart/AsBuiltReport.Chart.psm1 +++ b/AsBuiltReport.Chart/AsBuiltReport.Chart.psm1 @@ -5,15 +5,15 @@ switch ($PSVersionTable.PSEdition) { $architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture if ($architecture -eq "Arm64" -or $architecture -eq "Arm") { Write-Verbose "Architecture: ARM (Apple Silicon)" - Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}mac-osx{0}osx-arm64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) -Verbose -Debug + Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}mac-osx{0}osx-arm64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) } elseif ($architecture -eq "X64" -or $architecture -eq "X86") { Write-Verbose "Architecture: x86 (Intel)" - Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}mac-osx{0}osx-x64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) -Verbose -Debug + Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}mac-osx{0}osx-x64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) } else { Write-Verbose "Architecture: Unknown or other architecture" - Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}mac-osx{0}osx-arm64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) -Verbose -Debug + Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}mac-osx{0}osx-arm64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) } } elseif ($IsLinux) { Import-Module ("$PSScriptRoot{0}Src{0}Assemblies{0}Core{0}linux-x64{0}AsBuiltReportChart.dll" -f [System.IO.Path]::DirectorySeparatorChar) diff --git a/AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/dummy b/AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/dummy deleted file mode 100644 index e69de29..0000000 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aed89a..4039d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ 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.0] - 2026-03-14 + +### Added + +- Add Signal chart support +- Add support for setting the background color of the chart +- Add documentation example on how to use the modules + +### Changed + +Update module version to 0.3.0 + +### Fixed + +- Fix issue with the stacked chart not rendering correctly when using certain data sets +- Fix pester test to properly validate the functionality of the module + ## [0.2.0] - 2026-02-20 ### Added @@ -19,4 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Initial release of AsBuiltReport Chart, providing basic charting capabilities for AsBuiltReport data visualization \ No newline at end of file + +- Initial release of AsBuiltReport Chart, providing basic charting capabilities for AsBuiltReport data visualization + + diff --git a/Examples/Example01.ps1 b/Examples/Example01.ps1 new file mode 100644 index 0000000..c460c65 --- /dev/null +++ b/Examples/Example01.ps1 @@ -0,0 +1,61 @@ +<# + .SYNOPSIS + Example 01 - Basic Pie Chart + + .DESCRIPTION + This example demonstrates how to create a basic Pie Chart using the AsBuiltReport.Chart module. + The chart displays a simple breakdown of VM power states across a vSphere environment. +#> + +[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. + In a real-world scenario these values would come from your infrastructure query. +#> + +$ChartTitle = 'VM Power States' +$Values = @(120, 35, 10) +$Labels = @('Powered On', 'Powered Off', 'Suspended') + +<# + The New-PieChart cmdlet generates the Pie Chart image. + + -Title : Sets the chart title displayed at the top of the image. + -Values : Array of numeric values, one per slice. + -Labels : Array of label strings corresponding to each value. + -Format : Output file format (e.g. png, jpg, svg). + -OutputFolderPath : Directory where the generated chart file will be saved. + -Width : Width of the chart image in pixels. + -Height : Height of the chart image in pixels. + -ColorPalette : Predefined color palette (e.g. Category20, Pastel). + -Filename : Name of the output file (without extension). +#> + +New-PieChart ` + -Title $ChartTitle ` + -Values $Values ` + -Labels $Labels ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Width 600 ` + -Height 400 ` + -ColorPalette Category20 ` + -Filename 'Example01-PieChart' diff --git a/Examples/Example02.ps1 b/Examples/Example02.ps1 new file mode 100644 index 0000000..7d7af8f --- /dev/null +++ b/Examples/Example02.ps1 @@ -0,0 +1,98 @@ +<# + .SYNOPSIS + Example 02 - Pie Chart with Legend, Custom Colors and Border + + .DESCRIPTION + This example demonstrates how to create a Pie Chart with additional visual options, including: + - An enabled legend with custom alignment and orientation + - A custom hex color palette + - A chart border + - Adjusted title and label font sizes + - Custom chart dimensions + + The chart displays a server operating system distribution report. +#> + +[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. + In a real-world scenario these values would come from your infrastructure query. +#> + +$ChartTitle = 'Server OS Distribution' +$Values = @(85, 60, 30, 15) +$Labels = @('Windows Server 2022', 'Windows Server 2019', 'RHEL 9', 'Ubuntu 22.04') + +<# + A custom hex color palette can be used to match corporate branding or improve readability. + Each color corresponds to a slice in the order they appear in $Values. +#> + +$CustomColors = @('#0078D4', '#00B7C3', '#E74C3C', '#F39C12') + +<# + The New-PieChart cmdlet generates the Pie Chart image. + + -Title : Sets the chart title. + -TitleFontSize : Sets the font size of the title in points. + -TitleFontBold : Renders the title in bold. + -Values : Array of numeric values, one per slice. + -Labels : Array of label strings corresponding to each value. + -LabelFontSize : Sets the font size of the slice labels. + -LabelDistance : Controls how far labels are placed from the chart center (0.5-0.9). + -EnableLegend : Enables the legend on the chart. + -LegendAlignment : Positions the legend (e.g. UpperRight, LowerCenter). + -LegendOrientation : Sets legend layout direction (Vertical or Horizontal). + -LegendFontSize : Sets the legend text font size in points. + -EnableChartBorder : Draws a border around the chart area. + -ChartBorderColor : Sets the border color. + -ChartBorderSize : Sets the border thickness in pixels. + -EnableCustomColorPalette : Enables use of the custom color palette. + -CustomColorPalette : Array of hex color strings for each slice. + -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). +#> + +New-PieChart ` + -Title $ChartTitle ` + -TitleFontSize 18 ` + -TitleFontBold ` + -Values $Values ` + -Labels $Labels ` + -LabelFontSize 13 ` + -LabelDistance 0.7 ` + -EnableLegend ` + -LegendAlignment UpperRight ` + -LegendOrientation Vertical ` + -LegendFontSize 12 ` + -EnableChartBorder ` + -ChartBorderColor Black ` + -ChartBorderSize 2 ` + -EnableCustomColorPalette ` + -CustomColorPalette $CustomColors ` + -Width 600 ` + -Height 400 ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Filename 'Example02-PieChart-Advanced' diff --git a/Examples/Example03.ps1 b/Examples/Example03.ps1 new file mode 100644 index 0000000..e2198b1 --- /dev/null +++ b/Examples/Example03.ps1 @@ -0,0 +1,61 @@ +<# + .SYNOPSIS + Example 03 - Basic Bar Chart + + .DESCRIPTION + This example demonstrates how to create a basic Bar Chart using the AsBuiltReport.Chart module. + The chart displays CPU utilization across a set of ESXi hosts. +#> + +[CmdletBinding()] +param ( + [System.IO.FileInfo] $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. + In a real-world scenario these values would come from your infrastructure query. +#> + +$ChartTitle = 'ESXi Host CPU Utilization (%)' +$Values = @(72, 45, 88, 61, 53) +$Labels = @('esxi-host-01', 'esxi-host-02', 'esxi-host-03', 'esxi-host-04', 'esxi-host-05') + +<# + The New-BarChart cmdlet generates the Bar Chart image. + + -Title : Sets the chart title displayed at the top of the image. + -Values : Array of numeric values, one per bar. + -Labels : Array of label strings corresponding to each bar. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -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). +#> + +New-BarChart ` + -Title $ChartTitle ` + -Values $Values ` + -Labels $Labels ` + -LabelXAxis 'Host' ` + -LabelYAxis 'CPU (%)' ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Width 600 ` + -Height 600 ` + -Filename 'Example03-BarChart' diff --git a/Examples/Example04.ps1 b/Examples/Example04.ps1 new file mode 100644 index 0000000..4baa56c --- /dev/null +++ b/Examples/Example04.ps1 @@ -0,0 +1,96 @@ +<# + .SYNOPSIS + Example 04 - Bar Chart with Advanced Options + + .DESCRIPTION + This example demonstrates how to create a Bar Chart with advanced options, including: + - Horizontal bar orientation + - Custom color palette + - Bold title and labels + - Custom axis labels and chart dimensions + - Chart border + + The chart displays memory utilization across a set of physical servers. +#> + +[CmdletBinding()] +param ( + [System.IO.FileInfo] $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. + In a real-world scenario these values would come from your infrastructure query. +#> + +$ChartTitle = 'Physical Server Memory Utilization (%)' +$Values = @(91, 67, 78, 55, 83, 44) +$Labels = @('srv-prod-01', 'srv-prod-02', 'srv-prod-03', 'srv-dev-01', 'srv-dev-02', 'srv-test-01') + +<# + A custom hex color palette can be used to match corporate branding or improve readability. + Each color corresponds to a bar in the order it appears in $Values. +#> + +$CustomColors = @('#E74C3C', '#E67E22', '#F1C40F', '#2ECC71', '#3498DB', '#9B59B6') + +<# + The New-BarChart cmdlet generates the Bar Chart image. + + -Title : Sets the chart title. + -TitleFontSize : Sets the font size of the title in points. + -TitleFontBold : Renders the title in bold. + -Values : Array of numeric values, one per bar. + -Labels : Array of label strings corresponding to each bar. + -LabelFontSize : Sets the font size of the bar labels. + -LabelBold : Renders the bar labels in bold. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -AreaOrientation : Orientation of the bars (Horizontal or Vertical). + -AxesMarginsTop : Top margin for the chart area as a fraction (0-1). + -EnableChartBorder : Draws a border around the chart area. + -ChartBorderColor : Sets the border color. + -EnableCustomColorPalette : Enables use of the custom color palette. + -CustomColorPalette : Array of hex color strings for each bar. + -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). +#> + +New-BarChart ` + -Title $ChartTitle ` + -TitleFontSize 16 ` + -TitleFontBold ` + -Values $Values ` + -Labels $Labels ` + -LabelFontSize 12 ` + -LabelBold ` + -LabelXAxis 'Memory (%)' ` + -LabelYAxis 'Server' ` + -AreaOrientation Horizontal ` + -AxesMarginsTop 0.15 ` + -EnableChartBorder ` + -ChartBorderColor Black ` + -EnableCustomColorPalette ` + -CustomColorPalette $CustomColors ` + -Width 700 ` + -Height 450 ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Filename 'Example04-BarChart-Advanced' diff --git a/Examples/Example05.ps1 b/Examples/Example05.ps1 new file mode 100644 index 0000000..6d658df --- /dev/null +++ b/Examples/Example05.ps1 @@ -0,0 +1,88 @@ +<# + .SYNOPSIS + Example 05 - Basic Stacked Bar Chart + + .DESCRIPTION + This example demonstrates how to create a basic Stacked Bar Chart using the AsBuiltReport.Chart module. + The chart displays datastore utilization (used vs. free space) for a set of vSphere datastores. +#> + +[CmdletBinding()] +param ( + [System.IO.DirectoryInfo] $Path = Get-Location, + [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 Stacked Bar Chart: + - $Values is an array of double arrays. Each inner array represents one bar and contains the + values for all stack segments (categories) for that bar, in the same order as $LegendCategories. + The number of inner arrays should match $Labels.Length, and the length of each inner array + should match $LegendCategories.Length. + - $Labels contains the label for each bar (X-axis entries). + - $LegendCategories contains the label for each series (stack segments shown in the legend). + + In this example: + - Bar 1 = datastore-01: 800 GB Used, 200 GB Free + - Bar 2 = datastore-02: 600 GB Used, 400 GB Free + - Bar 3 = datastore-03: 1500 GB Used, 500 GB Free + - Bar 4 = datastore-04: 300 GB Used, 700 GB Free +#> + +$ChartTitle = 'Datastore Capacity (GB)' +$Labels = @('datastore-01', 'datastore-02', 'datastore-03', 'datastore-04') +$LegendCategories = @('Used Space', 'Free Space') + +# Each inner array represents one bar's segment values, ordered by $LegendCategories. +$Values = @(@(800, 200), @(600, 400), @(1500, 500), @(300, 700)) + +<# + The New-StackedBarChart cmdlet generates the Stacked Bar Chart image. + + -Title : Sets the chart title displayed at the top of the image. + -Values : Array of double arrays. Each inner array represents one bar and contains + the values for each stack segment (category) for that bar, ordered to match + $LegendCategories. The number of inner arrays should equal the number of + $Labels. + -Labels : Array of label strings, one per bar (X-axis entries). + -LegendCategories : Array of category names shown in the legend (one per value position in each inner array). + -EnableLegend : Enables the legend on the chart. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -Format : Output file format (e.g. png, jpg, svg). + -OutputFolderPath : Directory where the generated chart file will be saved. + -Width : Width of the output image in pixels. + -Height : Height of the output image in pixels. + -AreaOrientation : Orientation of the bars (Horizontal or Vertical). + -Filename : Name of the output file (without extension). +#> + +New-StackedBarChart ` + -Title $ChartTitle ` + -Values $Values ` + -Labels $Labels ` + -LegendCategories $LegendCategories ` + -EnableLegend ` + -LabelXAxis 'Datastore' ` + -LabelYAxis 'Capacity (GB)' ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Width 700 ` + -Height 450 ` + -AreaOrientation Horizontal ` + -Filename 'Example05-StackedBarChart' diff --git a/Examples/Example06.ps1 b/Examples/Example06.ps1 new file mode 100644 index 0000000..28cad59 --- /dev/null +++ b/Examples/Example06.ps1 @@ -0,0 +1,115 @@ +<# + .SYNOPSIS + Example 06 - Stacked Bar Chart with Advanced Options + + .DESCRIPTION + This example demonstrates how to create a Stacked Bar Chart with advanced options, including: + - Custom color palette + - Horizontal legend alignment at the top of the chart + - Bold title with larger font + - Custom axis labels + - Larger chart dimensions + + The chart displays network traffic breakdown (Inbound, Outbound, Dropped) per network adapter. +#> + +[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 Stacked Bar Chart: + - $Values is an array of double arrays. Each inner array contains the values for all categories + (stack segments) for a single bar. + - $Labels contains the label for each bar (X-axis entries). + - $LegendCategories contains the label for each series (stack segments shown in the legend). + + In this example each bar represents a network adapter, and each stack segment is a traffic type. +#> + +$ChartTitle = 'Network Adapter Traffic (Mbps)' +$Labels = @('vmnic0', 'vmnic1', 'vmnic2', 'vmnic3') +$LegendCategories = @('Inbound', 'Outbound', 'Dropped') + +# Values are organized by bar (network adapter), so each inner array corresponds to one adapter and +# contains the values for each traffic type (stack segment) in the order of $LegendCategories. +# Example values for Inbound, Outbound, and Dropped traffic for each network adapter: +# - vmnic0: 450 Mbps Inbound, 320 Mbps Outbound, 280 Mbps Dropped +$vmnic0 = [double[]]@(450, 320, 280) +$vmnic1 = [double[]]@(380, 290, 210) +$vmnic2 = [double[]]@(150, 50, 300) +$vmnic3 = [double[]]@(200, 320, 300) +$Values = @($vmnic0, $vmnic1, $vmnic2, $vmnic3) + +<# + A custom hex color palette can be used to match corporate branding or improve readability. + Each color corresponds to a stack segment category in the order they appear in $LegendCategories. +#> + +$CustomColors = @('#3498DB', '#2ECC71', '#E74C3C') + +<# + The New-StackedBarChart cmdlet generates the Stacked Bar Chart image. + + -Title : Sets the chart title. + -TitleFontSize : Sets the font size of the title in points. + -TitleFontBold : Renders the title in bold. + -Values : Array of double arrays, one per stack segment category. + -Labels : Array of label strings, one per bar (X-axis entries). + -LegendCategories : Array of category 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. UpperCenter, UpperRight). + -LegendFontSize : Sets the legend text font size in points. + -LabelFontSize : Sets the font size of the axis labels. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -AxesMarginsTop : Top margin for the chart area as a fraction (0-1). + -EnableCustomColorPalette : Enables use of the custom color palette. + -CustomColorPalette : Array of hex color strings, one per stack segment category. + -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). +#> + +New-StackedBarChart ` + -Title $ChartTitle ` + -TitleFontSize 18 ` + -TitleFontBold ` + -Values $Values ` + -Labels $Labels ` + -LegendCategories $LegendCategories ` + -EnableLegend ` + -LegendOrientation Horizontal ` + -LegendAlignment UpperCenter ` + -LegendFontSize 12 ` + -LabelFontSize 13 ` + -LabelXAxis 'Network Adapter' ` + -LabelYAxis 'Traffic (Mbps)' ` + -AxesMarginsTop 1 ` + -EnableCustomColorPalette ` + -CustomColorPalette $CustomColors ` + -Width 700 ` + -Height 450 ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Filename 'Example06-StackedBarChart-Advanced' diff --git a/Examples/Example07.ps1 b/Examples/Example07.ps1 new file mode 100644 index 0000000..3564272 --- /dev/null +++ b/Examples/Example07.ps1 @@ -0,0 +1,68 @@ +<# + .SYNOPSIS + Example 07 - Basic Signal Chart (Line Chart) + + .DESCRIPTION + This example demonstrates how to create a basic Signal Chart (line chart) using the + AsBuiltReport.Chart module. + + The Signal Chart is used to visualize sequential data points as a continuous line. In this + example, a single line representing hourly CPU utilization over a 24-hour period is plotted. +#> + +[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 Signal Chart: + - $Values is an array of double arrays. Each inner array represents a single line/series. + - A single inner array produces a single line on the chart. + + In this example, 24 hourly CPU utilization samples are plotted as a single line. + Data points are evenly spaced along the X-axis (using the default Period of 1.0). +#> + +$ChartTitle = 'CPU Utilization - Last 24 Hours (%)' +$CpuData = [double[]]@(12, 18, 15, 10, 8, 11, 25, 42, 65, 71, 68, 72, 74, 70, 66, 63, 58, 55, 48, 40, 35, 28, 22, 16) +$Values = @(, $CpuData) + +<# + The New-SignalChart cmdlet generates the Signal Chart image. + + -Title : Sets the chart title displayed at the top of the image. + -Values : Array of double arrays. Each inner array is one signal line/series. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -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). +#> + +New-SignalChart ` + -Title $ChartTitle ` + -Values $Values ` + -LabelXAxis 'Hour' ` + -LabelYAxis 'CPU (%)' ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Width 700 ` + -Height 450 ` + -Filename 'Example07-SignalChart' diff --git a/Examples/Example08.ps1 b/Examples/Example08.ps1 new file mode 100644 index 0000000..b066376 --- /dev/null +++ b/Examples/Example08.ps1 @@ -0,0 +1,83 @@ +<# + .SYNOPSIS + Example 08 - Signal Chart with DateTime X-Axis (Time-Series Data) + + .DESCRIPTION + This example demonstrates how to create a Signal Chart with a DateTime X-axis using the + AsBuiltReport.Chart module. + + When the -DateTimeTicksBottom switch is used, the X-axis is formatted as human-readable + date/time values. Data points are specified using OADate (OLE Automation Date) values which + can be obtained by calling .ToOADate() on a DateTime object. + + The chart displays hourly NFS read throughput for a NetApp ONTAP NAS volume over one day. +#> + +[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 time range for the X-axis. + OADate (OLE Automation Date) values are used to represent DateTime as a floating-point number. + Each increment of 1.0/24 represents one hour. +#> + +$StartDate = (Get-Date '2024-06-01 00:00:00').ToOADate() +$HourCount = 24 + +# Build an array of OADate values spaced one hour apart. +$XValues = [double[]](0..($HourCount - 1) | ForEach-Object { $StartDate + ($_ / 24.0) }) + +<# + Hourly NFS read throughput in MB/s for a 24-hour window. + In a real-world scenario these values would come from your storage monitoring system. +#> + +$NfsRead = [double[]]@(10.2, 12.5, 8.7, 6.3, 5.1, 7.4, 11.8, 15.6, 18.2, 20.1, 22.4, 19.8, 17.3, 16.5, 18.9, 21.2, 23.6, 20.8, 17.4, 14.2, 12.1, 10.5, 9.3, 8.8) +$Values = @(,$NfsRead) + +<# + The New-SignalChart cmdlet generates the Signal Chart image. + + -Title : Sets the chart title displayed at the top of the image. + -Values : Array of double arrays. Each inner array is one signal line/series. + -ScatterXValues : Array of double arrays containing OADate X values for each series. + When provided, scatter mode is used (explicit X positions per point). + -DateTimeTicksBottom : Formats the X-axis as human-readable date/time labels. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -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). +#> + +New-SignalChart ` + -Title 'ONTAP NAS - NFS Read Throughput (MB/s)' ` + -Values $Values ` + -ScatterXValues @(,$XValues) ` + -DateTimeTicksBottom ` + -LabelXAxis 'Time' ` + -LabelYAxis 'Throughput (MB/s)' ` + -Width 700 ` + -Height 400 ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Filename 'Example08-SignalChart-DateTime' diff --git a/Examples/Example09.ps1 b/Examples/Example09.ps1 new file mode 100644 index 0000000..5ed93a6 --- /dev/null +++ b/Examples/Example09.ps1 @@ -0,0 +1,118 @@ +<# + .SYNOPSIS + Example 09 - Signal Chart with Multiple Lines + + .DESCRIPTION + This example demonstrates how to create a Signal Chart with multiple lines and advanced visual + options using the AsBuiltReport.Chart module. + + Features demonstrated: + - Multiple signal lines on a single chart + - Scatter mode with explicit X values and DateTime X-axis + - Legend with custom font size + - Custom color palette + - Bold title and axis labels + + The chart displays hourly NFS read and write throughput for a NetApp ONTAP NAS volume over + one day, simulating performance data that would typically come from a monitoring system. +#> + +[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 time range for the X-axis. + OADate (OLE Automation Date) values are used to represent DateTime as a floating-point number. + Each increment of 1.0/24 represents one hour. +#> + +$StartDate = (Get-Date '2024-06-01 00:00:00').ToOADate() +$HourCount = 24 + +# Build an array of OADate values spaced one hour apart (shared by all lines). +$XValues = [double[]](0..($HourCount - 1) | ForEach-Object { $StartDate + ($_ / 24.0) }) + +<# + Hourly NFS throughput data in MB/s for a 24-hour window. + In a real-world scenario these values would come from your storage monitoring system. +#> + +$NfsRead = [double[]]@(10.2, 12.5, 8.7, 6.3, 5.1, 7.4, 11.8, 15.6, 18.2, 20.1, 22.4, 19.8, 17.3, 16.5, 18.9, 21.2, 23.6, 20.8, 17.4, 14.2, 12.1, 10.5, 9.3, 8.8) +$NfsWrite = [double[]]@(5.1, 6.2, 4.3, 3.1, 2.5, 3.7, 5.9, 7.8, 9.1, 10.0, 11.2, 9.9, 8.6, 8.2, 9.4, 10.6, 11.8, 10.4, 8.7, 7.1, 6.0, 5.2, 4.6, 4.4) + +# Each inner array is a separate line. The outer @() creates the list of series. +$Values = @($NfsRead, $NfsWrite) +$Labels = @('NFS Read', 'NFS Write') + +# ScatterXValues must match $Values in structure: one X array per Y array. +$ScatterXValues = @($XValues, $XValues) + +<# + Custom colors allow signal lines to be visually distinguished. + Colors are assigned in the same order as $Values. +#> + +$CustomColors = @('#3498DB', '#E74C3C') + +<# + The New-SignalChart cmdlet generates the Signal 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 : Array of double arrays. Each inner array is one signal line/series. + -Labels : Array of label strings, one per series (shown in the legend). + -ScatterXValues : Array of double arrays containing OADate X values for each series. + -DateTimeTicksBottom : Formats the X-axis as human-readable date/time labels. + -EnableLegend : Enables the legend on the chart. + -LegendOrientation : Sets legend layout direction (Vertical or Horizontal). + -LegendAlignment : Positions the legend (e.g. UpperRight, LowerCenter). + -LegendFontSize : Sets the legend text font size in points. + -LabelXAxis : Label for the X-axis. + -LabelYAxis : Label for the Y-axis. + -EnableCustomColorPalette : Enables use of the custom color palette. + -CustomColorPalette : Array of hex color strings, one per series. + -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). +#> + +New-SignalChart ` + -Title 'ONTAP NAS - NFS Throughput (MB/s)' ` + -TitleFontSize 18 ` + -TitleFontBold ` + -Values $Values ` + -Labels $Labels ` + -ScatterXValues $ScatterXValues ` + -DateTimeTicksBottom ` + -EnableLegend ` + -LegendOrientation Vertical ` + -LegendAlignment UpperRight ` + -LegendFontSize 12 ` + -LabelXAxis 'Time' ` + -LabelYAxis 'Throughput (MB/s)' ` + -EnableCustomColorPalette ` + -CustomColorPalette $CustomColors ` + -Width 800 ` + -Height 450 ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Filename 'Example09-SignalChart-MultiLine' diff --git a/README.md b/README.md index fb45ac2..89ab0de 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,7 @@ ## :exclamation: THIS ASBUILTREPORT MODULE IS CURRENTLY IN DEVELOPMENT AND MIGHT NOT YET BE FUNCTIONAL ❗ -AsBuiltReport.Chart is a PowerShell module which works in conjunction with [AsBuiltReport.Core](https://github.com/AsBuiltReport/AsBuiltReport.Core). - -[AsBuiltReport](https://github.com/AsBuiltReport/AsBuiltReport) is an open-sourced community project which utilises PowerShell to produce as-built documentation in multiple document formats for multiple vendors and technologies. - -Please refer to the AsBuiltReport [website](https://www.asbuiltreport.com) for more detailed information about this project. +AsBuiltReport.Chart is a PowerShell module which provides a set of cmdlets for generating charts and visualizations in As Built Reports. This module is designed to work seamlessly with the AsBuiltReport.Core module, allowing users to create visually appealing and informative reports with ease. # :beginner: Getting Started @@ -58,19 +54,19 @@ This report is compatible with the following PowerShell versions; ## 🗺️ Language Support -The AsBuiltReport Chart As Built Report supports the following languages; +The AsBuiltReport Chart supports the following languages; - English (US) (Default) ## :wrench: System Requirements -PowerShell 7, and the following PowerShell modules are required for generating a AsBuiltReport Chart. +PowerShell 5.1 or PowerShell 7, and the following PowerShell modules are required for generating an AsBuiltReport Chart. - [AsBuiltReport.Core Module](https://www.powershellgallery.com/packages/AsBuiltReport.Core/) ### :closed_lock_with_key: Required Privileges - - + +Local user privilege ## :package: Module Installation @@ -84,26 +80,86 @@ install-module AsBuiltReport.Chart -Force update-module AsBuiltReport.Chart -Force ``` -### GitHub +## :computer: Examples +Here are some examples to get you going. + +### Pie Chart +```powershell +# Generate a Pie Chart with the title 'Test', values of 1 and 2, labels 'A' and 'B', and export the chart in PNG format. Enable the legend and set the width to 600 pixels, height to 400 pixels, title font size to 20, and label font size to 16. +New-PieChart -Title 'Test' -Values @(1,2) -Labels @('A','B') -Format 'png' -EnableLegend -Width 600 -Height 400 -TitleFontSize 20 -LabelFontSize 16 +``` +![PieChart](./Samples/PieChart.png) + +### Bar Chart +```powershell +# Generate a Bar Chart with the title 'Test', values of 1 and 2, labels 'A' and 'B', and export the chart in PNG format. Enable the legend and set the width to 600 pixels, height to 400 pixels, title font size to 20, and label font size to 16. +New-BarChart -Title 'Test' -Values @(1,2) -Labels @('A','B') -Format 'png' -EnableLegend -Width 600 -Height 400 -TitleFontSize 20 -LabelFontSize 16 -AxesMarginsTop 1 +``` +![BarChart](./Samples/BarChart.png) -If you are unable to use the PowerShell Gallery, you can still install the module manually. Ensure you repeat the following steps for the [system requirements](https://github.com/AsBuiltReport/AsBuiltReport.Chart#wrench-system-requirements) also. +### Stacked Bar Chart +```powershell +# Generate a Stacked Bar Chart with the title 'Test', values @(1,2) for bar 'A' and @(3,4) for bar 'B' (one inner -Values array per bar matching -Labels), legend categories 'Value1' and 'Value2' for the stacked segments, and export the chart in PNG format. Enable the legend, set the legend orientation to horizontal, align the legend to the upper center, set the width to 600 pixels, height to 400 pixels, title font size to 20, label font size to 16, and axes margins top to 1. +New-StackedBarChart -Title 'Test' -Values @(@(1,2),@(3,4)) -Labels @('A','B') -LegendCategories @('Value1','Value2') -Format 'png' -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 400 -TitleFontSize 20 -LabelFontSize 16 -AxesMarginsTop 1 +``` +![StackedBarChart](./Samples/StackedBarChart.png) -1. Download the code package / [latest release](https://github.com/AsBuiltReport/AsBuiltReport.Chart/releases/latest) zip from GitHub -2. Extract the zip file -3. Copy the folder `AsBuiltReport.Chart` to a path that is set in `$env:PSModulePath`. -4. Open a PowerShell terminal window and unblock the downloaded files with +### :blue_book: Example Index + +All examples in the latest release of AsBuiltReport.Chart can be found in the table below. + +| Name | Description | +| ------------------------------------ | ---------------------------------------------------- | +| [Example1](./Examples/Example01.ps1) | Basic Pie Chart | +| [Example2](./Examples/Example02.ps1) | Pie Chart with Legend, Custom Colors and Border | +| [Example3](./Examples/Example03.ps1) | Basic Bar Chart | +| [Example4](./Examples/Example04.ps1) | Bar Chart with Advanced Options | +| [Example5](./Examples/Example05.ps1) | Basic Stacked Bar Chart | +| [Example6](./Examples/Example06.ps1) | Stacked Bar Chart with Advanced Options | +| [Example7](./Examples/Example07.ps1) | Basic Signal Chart (Line Chart) | +| [Example8](./Examples/Example08.ps1) | Signal Chart with DateTime X-Axis (Time-Series Data) | +| [Example9](./Examples/Example09.ps1) | Signal Chart with Multiple Lines | + +## Watermark Support + +All chart types support an optional watermark that overlays semi-transparent text in the center of the chart. The watermark is **disabled by default** and is activated only when the `-EnableWatermark` switch is supplied. + +### Watermark Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `EnableWatermark` | Switch | (off) | Enables the watermark overlay. | +| `WatermarkText` | String | `Confidential` | Text to display as the watermark. | +| `WatermarkFontName` | String | `Arial` | Font family for the watermark text. | +| `WatermarkFontSize` | Int | `24` | Font size (points) for the watermark text. | +| `WatermarkColor` | BasicColors | `Gray` | Color of the watermark text. | +| `WatermarkOpacity` | Double | `0.3` | Opacity (0.0–1.0) of the watermark. Lower values are more transparent. | + +### Watermark Examples + +```powershell +# Pie chart with default watermark (gray "Confidential" at 30% opacity) +New-PieChart -Title 'Sales' -Values @(10, 20, 30) -Labels @('A', 'B', 'C') -Format 'png' -EnableWatermark + +# Bar chart with a custom watermark text, color, and opacity +New-BarChart -Title 'Revenue' -Values @(100, 200, 150) -Labels @('Q1', 'Q2', 'Q3') -Format 'png' ` + -EnableWatermark -WatermarkText 'CONFIDENTIAL' -WatermarkColor Red -WatermarkOpacity 0.2 + +# Stacked bar chart with a larger watermark font +New-StackedBarChart -Title 'Budget' -Values @(@(1,2),@(3,4)) -Labels @('A','B') -LegendCategories @('X','Y') -Format 'png' ` + -EnableWatermark -WatermarkFontSize 36 -WatermarkText 'DRAFT' + +# Signal chart with a custom font and opacity +New-SignalChart -Title 'Throughput' -Values @(,[double[]]@(1,2,3,4,5)) -Format 'png' ` + -EnableWatermark -WatermarkFontName 'Arial' -WatermarkOpacity 0.5 +``` + +## :x: Known Issues + + - No known issues at this time. - ```powershell - $path = (Get-Module -Name AsBuiltReport.Chart -ListAvailable).ModuleBase; Unblock-File -Path $path\*.psd1; Unblock-File -Path $path\Src\Public\*.ps1; Unblock-File -Path $path\Src\Private\*.ps1 - ``` -5. Close and reopen the PowerShell terminal window. -_Note: You are not limited to installing the module to those example paths, you can add a new entry to the environment variable PSModulePath if you want to use another path._ -### Options -The **Options** schema allows certain options within the report to be toggled on or off. -## :computer: Examples - diff --git a/Samples/BarChart.png b/Samples/BarChart.png new file mode 100644 index 0000000..2080150 Binary files /dev/null and b/Samples/BarChart.png differ diff --git a/Samples/PieChart.png b/Samples/PieChart.png new file mode 100644 index 0000000..ffabca3 Binary files /dev/null and b/Samples/PieChart.png differ diff --git a/Samples/StackedBarChart.png b/Samples/StackedBarChart.png new file mode 100644 index 0000000..fa910a0 Binary files /dev/null and b/Samples/StackedBarChart.png differ diff --git a/Sources/AsBuiltReportChart.cs b/Sources/AsBuiltReportChart.cs index 562d1a3..ccb4361 100644 --- a/Sources/AsBuiltReportChart.cs +++ b/Sources/AsBuiltReportChart.cs @@ -6,8 +6,8 @@ public static class MainEntry { public static void Main(string[] args) { - double[] values = new double[] { 3, 2 }; - string[] labels = new string[] { "Cassa", "Carro" }; + double[] values = new double[] { 3}; + string[] labels = new string[] { "Cassa"}; Chart.EnableLegend = true; Chart.LegendFontColor = BasicColors.Black; @@ -31,7 +31,7 @@ public static void Main(string[] args) // Chart.ColorPalette = ColorPalettes.Dark; - Chart.AreaOrientation = Orientations.Horizontal; + Chart.AreaOrientation = Orientations.Vertical; Bar PieBar = new Bar(); Chart.Format = Formats.png; @@ -39,7 +39,7 @@ public static void Main(string[] args) Chart.InvertCustomColorPalette = true; Chart.CustomColorPalette = new string[] { "#DFF0D0", "#FFF4C7", "#FEDDD7", "#878787", "#77a898", "#5e9584", "#458370", "#2a715d", "#005f4b" }; - PieBar.Chart(values, labels, width: 600, height: 400, filename: "PieChartExample"); + PieBar.Chart(values, labels, width: 600, height: 600, filename: "PieChartExample"); } } } \ No newline at end of file diff --git a/Sources/BarChart.cs b/Sources/BarChart.cs index 66556eb..5002f17 100644 --- a/Sources/BarChart.cs +++ b/Sources/BarChart.cs @@ -22,7 +22,7 @@ public object Chart(double[] values, string[] labels, string filename = "output" } else { - throw new Exception("CustomColorPalette is empty. Please provide valid color values."); + throw new InvalidOperationException("CustomColorPalette is empty. Please provide valid color values."); } } else @@ -187,8 +187,46 @@ public object Chart(double[] values, string[] labels, string filename = "output" // Set margins settings myPlot.Axes.Margins(left: AxesMarginsLeft, right: AxesMarginsRight, bottom: AxesMarginsDown, top: AxesMarginsTop); - // Set filepath + // Set background colors + if (FigureBackgroundColor.HasValue) + { + myPlot.FigureBackground.Color = GetDrawingColor(FigureBackgroundColor.Value); + } + if (DataBackgroundColor.HasValue) + { + myPlot.DataBackground.Color = GetDrawingColor(DataBackgroundColor.Value); + } + + // Set axis limits if values are empty or contain only one value to prevent auto-scaling issues + if (values.Length == 1) + { + double singleValue = values[0]; + double padding = Math.Abs(singleValue) * 0.1; + if (padding == 0) + padding = 1; + + if (AreaOrientation == Orientations.Horizontal) + { + // 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); + } + 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); + } + } + // Apply watermark if enabled + ApplyWatermark(myPlot); + + // Set filepath string Filepath = _outputFolderPath ?? System.IO.Directory.GetCurrentDirectory(); // Save Plot diff --git a/Sources/Chart.cs b/Sources/Chart.cs index 086fc62..39535db 100644 --- a/Sources/Chart.cs +++ b/Sources/Chart.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.IO; +using System.Security.Cryptography; using System.Text.RegularExpressions; using AsBuiltReportChart.Enums; @@ -101,6 +102,7 @@ public static double AreaExplodeFraction { ColorPalettes.Aurora, new ScottPlot.Palettes.Aurora() }, { ColorPalettes.Building, new ScottPlot.Palettes.Building() }, { ColorPalettes.ColorblindFriendly, new ScottPlot.Palettes.ColorblindFriendly() }, + { ColorPalettes.ColorblindFriendlyDark, new ScottPlot.Palettes.ColorblindFriendlyDark() }, { ColorPalettes.Dark, new ScottPlot.Palettes.Dark() }, { ColorPalettes.DarkPastel, new ScottPlot.Palettes.DarkPastel() }, { ColorPalettes.Frost, new ScottPlot.Palettes.Frost() }, @@ -138,7 +140,7 @@ public static ColorPalettes? ColorPalette internal static string[] _customColorPalette; public static string[] CustomColorPalette { - get => _customColorPalette ?? new string[0]; + get => _customColorPalette ?? Array.Empty(); set { if (value != null && value.Length > 0) @@ -196,11 +198,10 @@ public static string OutputFolderPath public static bool IsValidHexColor(string hexCode) { // Regex for #RGB, #RRGGBB, #RGBA, or #RRGGBBAA formats (case-insensitive) - var regex = MyRegex(); - return regex.IsMatch(hexCode); + return HexColorRegex.IsMatch(hexCode); } - private static Regex MyRegex() => new Regex("^#([A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$"); + private static readonly Regex HexColorRegex = new Regex("^#([A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$", RegexOptions.Compiled); internal static readonly IReadOnlyDictionary LegendBorderStyleMap = new Dictionary() { @@ -253,6 +254,19 @@ public static bool IsValidHexColor(string hexCode) { BasicColors.DarkGreen, Colors.DarkGreen }, }; + internal static readonly IReadOnlyDictionary WatermarkAlignmentMap = new Dictionary() + { + {Alignments.LowerCenter, Alignment.LowerCenter}, + {Alignments.LowerLeft,Alignment.LowerLeft}, + {Alignments.LowerRight, Alignment.LowerRight}, + {Alignments.MiddleCenter,Alignment.MiddleCenter}, + {Alignments.MiddleLeft, Alignment.MiddleLeft}, + {Alignments.MiddleRight,Alignment.MiddleRight}, + {Alignments.UpperCenter,Alignment.UpperCenter}, + {Alignments.UpperLeft,Alignment.UpperLeft}, + {Alignments.UpperRight, Alignment.UpperRight}, + }; + // Set area axes margins internal static double _axesMarginsTop = 0.07; public static double AxesMarginsTop @@ -319,15 +333,117 @@ public static double AxesMarginsRight } } + // Chart background color settings (All Charts) + public static BasicColors? FigureBackgroundColor { get; set; } + public static BasicColors? DataBackgroundColor { get; set; } + + // Watermark settings (All Charts) + public static bool EnableWatermark { get; set; } + public static string WatermarkText { get; set; } = "Confidential"; + + public static Alignments WatermarkAlignment { get; set; } = Alignments.MiddleCenter; + + public static float WatermarkRotation { get; set; } = 0; + + public static string WatermarkFontName { get; set; } = "Arial"; + public static int WatermarkFontSize { get; set; } = 24; + public static BasicColors WatermarkColor { get; set; } = BasicColors.Gray; + + internal static double _watermarkOpacity = 0.3; + public static double WatermarkOpacity + { + get { return _watermarkOpacity; } + set + { + if (value >= 0.0 && value <= 1.0) + { + _watermarkOpacity = value; + } + else + { + throw new ArgumentException("Error: WatermarkOpacity value range must be from 0.0 to 1.0."); + } + } + } + + internal static void ApplyWatermark(Plot plot) + { + if (!EnableWatermark || string.IsNullOrEmpty(WatermarkText)) + return; + + var annotation = plot.Add.Annotation(WatermarkText, WatermarkAlignmentMap[WatermarkAlignment]); + annotation.LabelFontColor = ColorMap[WatermarkColor].WithOpacity(WatermarkOpacity); + annotation.LabelFontSize = WatermarkFontSize; + annotation.LabelFontName = WatermarkFontName; + annotation.LabelBackgroundColor = Colors.Transparent; + annotation.LabelBorderColor = Colors.Transparent; + annotation.LabelBorderWidth = 0; + annotation.LabelShadowColor = Colors.Transparent; + annotation.LabelRotation = WatermarkRotation; + } + public static Color GetDrawingColor(BasicColors color) { return ColorMap[color]; } + + internal static void Reset() + { + Format = Formats.png; + Title = null; + TitleFontBold = false; + TitleFontSize = 14; + TitleFontColor = BasicColors.Black; + FontName = "Arial"; + LabelFontSize = 14; + LabelFontColor = BasicColors.Black; + LabelBold = false; + LabelYAxis = "Count"; + LabelXAxis = "Values"; + _labelDistance = 0.6; + AreaOrientation = Orientations.Vertical; + _areaExplodeFraction = 0; + EnableLegend = false; + LegendFontSize = 12; + LegendFontColor = BasicColors.Black; + LegendBold = false; + LegendBorderStyle = BorderStyles.Solid; + LegendBorderSize = 1; + LegendBorderColor = BasicColors.Black; + LegendOrientation = Orientations.Vertical; + LegendAlignment = Alignments.LowerRight; + EnableChartBorder = false; + ChartBorderStyle = BorderStyles.Solid; + ChartBorderSize = 1; + ChartBorderColor = BasicColors.Black; + colorPalette = ColorPaletteMap[ColorPalettes.Category10]; + InvertCustomColorPalette = false; + _customColorPalette = null; + EnableCustomColorPalette = false; + _outputFolderPath = null; + _axesMarginsTop = 0.07; + _axesMarginsDown = 0.07; + _axesMarginsLeft = 0.05; + _axesMarginsRight = 0.05; + FigureBackgroundColor = null; + DataBackgroundColor = null; + EnableWatermark = false; + WatermarkText = "Confidential"; + WatermarkAlignment = Alignments.MiddleCenter; + WatermarkRotation = 0; + WatermarkFontName = "Arial"; + WatermarkFontSize = 24; + WatermarkColor = BasicColors.Gray; + _watermarkOpacity = 0.3; + } + public static string GenerateToken(Byte length) { var bytes = new byte[length]; - var rnd = new Random(); - rnd.NextBytes(bytes); + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(bytes); + } return Convert.ToBase64String(bytes).Replace("=", "").Replace("+", "").Replace("/", ""); } public static object SaveInFormat(Plot plot, int width, int height, string filepath, string filename, Formats Format) @@ -346,10 +462,10 @@ public static object SaveInFormat(Plot plot, int width, int height, string filep throw new ArgumentException("Error: Unable to Export Chart Exception"); } case Formats.jpg: - plot.SaveJpeg(Path.Combine(filepath, $"{filename}.jpeg"), width, height); - if (File.Exists(Path.Combine(filepath, $"{filename}.jpeg"))) + plot.SaveJpeg(Path.Combine(filepath, $"{filename}.jpg"), width, height); + if (File.Exists(Path.Combine(filepath, $"{filename}.jpg"))) { - FileInfo fileInfo = new FileInfo(Path.Combine(filepath, $"{filename}.jpeg")); + FileInfo fileInfo = new FileInfo(Path.Combine(filepath, $"{filename}.jpg")); return fileInfo; } else @@ -357,10 +473,10 @@ public static object SaveInFormat(Plot plot, int width, int height, string filep throw new ArgumentException("Error: Unable to Export Chart Exception"); } case Formats.jpeg: - plot.SaveJpeg(Path.Combine(filepath, $"{filename}.jpg"), width, height); - if (File.Exists(Path.Combine(filepath, $"{filename}.jpg"))) + plot.SaveJpeg(Path.Combine(filepath, $"{filename}.jpeg"), width, height); + if (File.Exists(Path.Combine(filepath, $"{filename}.jpeg"))) { - FileInfo fileInfo = new FileInfo(Path.Combine(filepath, $"{filename}.jpg")); + FileInfo fileInfo = new FileInfo(Path.Combine(filepath, $"{filename}.jpeg")); return fileInfo; } else @@ -400,16 +516,7 @@ public static object SaveInFormat(Plot plot, int width, int height, string filep throw new ArgumentException("Error: Unable to Export Chart Exception"); } default: - plot.SavePng(Path.Combine(filepath, $"{filename}.png"), width, height); - if (File.Exists(Path.Combine(filepath, $"{filename}.png"))) - { - FileInfo fileInfo = new FileInfo(Path.Combine(filepath, $"{filename}.png")); - return fileInfo; - } - else - { - throw new ArgumentException("Error: Unable to Export Chart Exception"); - } + throw new ArgumentException($"Error: Unsupported format '{Format}'."); } } } diff --git a/Sources/PieChart.cs b/Sources/PieChart.cs index c87fbce..12127bb 100644 --- a/Sources/PieChart.cs +++ b/Sources/PieChart.cs @@ -21,7 +21,7 @@ public object Chart(double[] values, string[] labels, string filename = "output" } else { - throw new Exception("CustomColorPalette is empty. Please provide valid color values."); + throw new InvalidOperationException("CustomColorPalette is empty. Please provide valid color values."); } } @@ -98,6 +98,19 @@ public object Chart(double[] values, string[] labels, string filename = "output" // Set margins settings 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); + } + + // Apply watermark if enabled + ApplyWatermark(myPlot); + // Set filetpath to save string Filepath = _outputFolderPath ?? Directory.GetCurrentDirectory(); diff --git a/Sources/PowerShell/BarChartPwsh.cs b/Sources/PowerShell/BarChartPwsh.cs index 13cf3c4..262f0a0 100644 --- a/Sources/PowerShell/BarChartPwsh.cs +++ b/Sources/PowerShell/BarChartPwsh.cs @@ -121,6 +121,37 @@ public class NewBarChartCommand : Cmdlet [Parameter(Mandatory = false, HelpMessage = "Right margin for the chart area as a percentage (0-1).")] 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; + // Set chart Size WxH [Parameter(Mandatory = false, HelpMessage = "Width of the output chart in pixels. Defaults to 400.")] public int Width { get; set; } = 400; @@ -134,6 +165,7 @@ public class NewBarChartCommand : Cmdlet protected override void ProcessRecord() { + Chart.Reset(); if (Values != null && Labels != null) { @@ -174,7 +206,7 @@ protected override void ProcessRecord() } else { - throw new Exception("EnableCustomColorPalette requires CustomColorPalette to be set."); + throw new InvalidOperationException("EnableCustomColorPalette requires CustomColorPalette to be set."); } } else @@ -210,6 +242,20 @@ protected override void ProcessRecord() Chart.AxesMarginsLeft = AxesMarginsLeft; Chart.AxesMarginsRight = AxesMarginsRight; + // Background color settings + Chart.FigureBackgroundColor = FigureBackgroundColor; + Chart.DataBackgroundColor = DataBackgroundColor; + + // Watermark settings + Chart.EnableWatermark = EnableWatermark; + Chart.WatermarkAlignment = WatermarkAlignment; + Chart.WatermarkText = WatermarkText; + Chart.WatermarkFontName = WatermarkFontName; + Chart.WatermarkFontSize = WatermarkFontSize; + Chart.WatermarkColor = WatermarkColor; + Chart.WatermarkOpacity = WatermarkOpacity; + Chart.WatermarkRotation = WatermarkRotation; + // Set file directory save path Chart.OutputFolderPath = OutputFolderPath; diff --git a/Sources/PowerShell/PieChartPwsh.cs b/Sources/PowerShell/PieChartPwsh.cs index bd8588c..5ccf2c2 100644 --- a/Sources/PowerShell/PieChartPwsh.cs +++ b/Sources/PowerShell/PieChartPwsh.cs @@ -115,6 +115,37 @@ public class NewPieChartCommand : Cmdlet [Parameter(Mandatory = false, HelpMessage = "Right margin for the chart area. Defaults to 0.05.")] 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; + // Set chart Size WxH [Parameter(Mandatory = false, HelpMessage = "Width of the chart in pixels. Defaults to 400.")] public int Width { get; set; } = 400; @@ -129,6 +160,7 @@ public class NewPieChartCommand : Cmdlet protected override void ProcessRecord() { + Chart.Reset(); if (Values != null && Labels != null) { if (EnableLegend) @@ -168,7 +200,7 @@ protected override void ProcessRecord() } else { - throw new Exception("EnableCustomColorPalette requires CustomColorPalette to be set."); + throw new InvalidOperationException("EnableCustomColorPalette requires CustomColorPalette to be set."); } } else @@ -200,6 +232,20 @@ protected override void ProcessRecord() Chart.LabelFontColor = LabelFontColor; Chart.LabelBold = LabelBold; + // Background color settings + Chart.FigureBackgroundColor = FigureBackgroundColor; + Chart.DataBackgroundColor = DataBackgroundColor; + + // Watermark settings + Chart.EnableWatermark = EnableWatermark; + Chart.WatermarkText = WatermarkText; + Chart.WatermarkAlignment = WatermarkAlignment; + Chart.WatermarkFontName = WatermarkFontName; + Chart.WatermarkFontSize = WatermarkFontSize; + Chart.WatermarkColor = WatermarkColor; + Chart.WatermarkOpacity = WatermarkOpacity; + Chart.WatermarkRotation = WatermarkRotation; + // Set file directory save path Chart.OutputFolderPath = OutputFolderPath; diff --git a/Sources/PowerShell/SignalChartPwsh.cs b/Sources/PowerShell/SignalChartPwsh.cs new file mode 100644 index 0000000..78c70bf --- /dev/null +++ b/Sources/PowerShell/SignalChartPwsh.cs @@ -0,0 +1,282 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Management.Automation; + +namespace AsBuiltReportChart.PowerShell +{ + [Cmdlet(VerbsCommon.New, "SignalChart")] + public class NewSignalChartCommand : Cmdlet + { + // Declare the parameters for the cmdlet. + [Parameter(Mandatory = false, HelpMessage = "Output filename for the chart. If not specified, a random token will be generated.")] + public string Filename { get; set; } = Chart.GenerateToken(8); + + [Parameter(Mandatory = true, HelpMessage = "List of double arrays representing each signal line to plot.")] + public List Values { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Array of labels for each signal line (used in the legend).")] + public string[] Labels { get; set; } + + // Title settings + [Parameter(Mandatory = true, HelpMessage = "Title text for the chart.")] + public string Title { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Make the title font bold.")] + public SwitchParameter TitleFontBold { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Font size for the title in points.")] + public int TitleFontSize { get; set; } = 14; + + [Parameter(Mandatory = false, HelpMessage = "Font color for the title.")] + public BasicColors TitleFontColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Enable the legend on the chart.")] + public SwitchParameter EnableLegend { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Orientation of the legend (Horizontal or Vertical).")] + public Enums.Orientations LegendOrientation { get; set; } = Enums.Orientations.Vertical; + + [Parameter(Mandatory = false, HelpMessage = "Alignment position of the legend on the chart.")] + public Enums.Alignments LegendAlignment { get; set; } = Enums.Alignments.UpperRight; + + [Parameter(Mandatory = false, HelpMessage = "Font size for the legend text in points.")] + public int LegendFontSize { get; set; } = 14; + + [Parameter(Mandatory = false, HelpMessage = "Make the legend font bold.")] + public SwitchParameter LegendBold { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Font color for the legend text.")] + public BasicColors LegendFontColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Border style for the legend box.")] + public Enums.BorderStyles LegendBorderStyle { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Border size for the legend box in pixels.")] + public int LegendBorderSize { get; set; } = 1; + + [Parameter(Mandatory = false, HelpMessage = "Border color for the legend box.")] + public BasicColors LegendBorderColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = true, HelpMessage = "Output format for the chart (PNG, JPEG, etc.).")] + public Formats Format { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Border color for the chart area.")] + public BasicColors ChartBorderColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Border size for the chart area in pixels.")] + public int ChartBorderSize { get; set; } = 1; + + [Parameter(Mandatory = false, HelpMessage = "Border style for the chart area.")] + public Enums.BorderStyles ChartBorderStyle { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Enable border around the chart area.")] + public SwitchParameter EnableChartBorder { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Color palette preset to use 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 = "Array of custom hex color codes for the signal lines.")] + public string[] CustomColorPalette { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Font name to use for all text in the chart.")] + public string FontName { get; set; } = "Arial"; + + // Label Font settings + [Parameter(Mandatory = false, HelpMessage = "Font size for axis labels in points.")] + public int LabelFontSize { get; set; } = 14; + + [Parameter(Mandatory = false, HelpMessage = "Font color for axis labels.")] + public BasicColors LabelFontColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Make axis label fonts bold.")] + public SwitchParameter LabelBold { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Label text for the Y-axis.")] + public string LabelYAxis { get; set; } = "Count"; + + [Parameter(Mandatory = false, HelpMessage = "Label text for the X-axis.")] + public string LabelXAxis { get; set; } = "Values"; + + // Signal-specific settings + [Parameter(Mandatory = false, HelpMessage = "X-axis offset for the signal data as an OADate value (use (Get-Date '2024-01-01').ToOADate() to convert from a DateTime).")] + public double XOffset { get; set; } = 0; + + [Parameter(Mandatory = false, HelpMessage = "List of double arrays representing the X values for each scatter line. When provided, scatter mode is used instead of signal mode. Use OADate values for DateTime X axes (e.g. (Get-Date '2024-01-01').ToOADate()).")] + public List ScatterXValues { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Period (interval) between each data point. Defaults to 1.0.")] + public double Period { get; set; } = 1.0; + + [Parameter(Mandatory = false, HelpMessage = "Display DateTime ticks on the bottom X axis.")] + public SwitchParameter DateTimeTicksBottom { get; set; } + + // Axes margins + [Parameter(Mandatory = false, HelpMessage = "Top margin for the chart area as a fraction (0-1).")] + public double AxesMarginsTop { get; set; } = 0.05; + + [Parameter(Mandatory = false, HelpMessage = "Bottom margin for the chart area as a fraction (0-1).")] + public double AxesMarginsDown { get; set; } = 0.05; + + [Parameter(Mandatory = false, HelpMessage = "Left margin for the chart area as a fraction (0-1).")] + public double AxesMarginsLeft { get; set; } = 0.05; + + [Parameter(Mandatory = false, HelpMessage = "Right margin for the chart area as a fraction (0-1).")] + 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; + + // Set chart Size WxH + [Parameter(Mandatory = false, HelpMessage = "Width of the output chart in pixels. Defaults to 400.")] + public int Width { get; set; } = 400; + + [Parameter(Mandatory = false, HelpMessage = "Height of the output chart in pixels. Defaults to 300.")] + public int Height { get; set; } = 300; + + // Set OutputFolderPath + [Parameter(Mandatory = false, HelpMessage = "Output folder path where the chart will be saved.")] + public string OutputFolderPath { get; set; } = Directory.GetCurrentDirectory(); + + protected override void ProcessRecord() + { + Chart.Reset(); + if (Values != null) + { + if (EnableLegend) + { + Chart.EnableLegend = EnableLegend; + // Legend box settings + Chart.LegendOrientation = LegendOrientation; + Chart.LegendAlignment = LegendAlignment; + + // Legend font settings + Chart.LegendFontSize = LegendFontSize; + Chart.LegendFontColor = LegendFontColor; + Chart.LegendBold = LegendBold; + // Legend border settings + Chart.LegendBorderStyle = LegendBorderStyle; + Chart.LegendBorderSize = LegendBorderSize; + Chart.LegendBorderColor = LegendBorderColor; + } + + if (EnableChartBorder) + { + // Chart area settings + Chart.EnableChartBorder = EnableChartBorder; + Chart.ChartBorderColor = ChartBorderColor; + Chart.ChartBorderSize = ChartBorderSize; + Chart.ChartBorderStyle = ChartBorderStyle; + } + + // Color palette settings + if (EnableCustomColorPalette) + { + if (CustomColorPalette != null && CustomColorPalette.Length > 0) + { + // Set ScottPlot custom color palette + Chart.EnableCustomColorPalette = EnableCustomColorPalette; + Chart.InvertCustomColorPalette = InvertCustomColorPalette; + Chart.CustomColorPalette = CustomColorPalette; + } + else + { + throw new InvalidOperationException("EnableCustomColorPalette requires CustomColorPalette to be set."); + } + } + else + { + Chart.ColorPalette = ColorPalette; + } + + // Title settings + if (Title != null) + { + Chart.Title = Title; + Chart.TitleFontBold = TitleFontBold; + Chart.TitleFontSize = TitleFontSize; + Chart.TitleFontColor = TitleFontColor; + } + + // Font Settings + Chart.FontName = FontName; + Chart.LabelFontSize = LabelFontSize; + Chart.LabelFontColor = LabelFontColor; + Chart.LabelBold = LabelBold; + + // Set font for the X and Y axis labels + Chart.LabelXAxis = LabelXAxis; + Chart.LabelYAxis = LabelYAxis; + + // Axes margins + Chart.AxesMarginsTop = AxesMarginsTop; + Chart.AxesMarginsDown = AxesMarginsDown; + Chart.AxesMarginsLeft = AxesMarginsLeft; + Chart.AxesMarginsRight = AxesMarginsRight; + + // Background color settings + Chart.FigureBackgroundColor = FigureBackgroundColor; + Chart.DataBackgroundColor = DataBackgroundColor; + + // Watermark settings + Chart.EnableWatermark = EnableWatermark; + Chart.WatermarkText = WatermarkText; + Chart.WatermarkAlignment = WatermarkAlignment; + Chart.WatermarkFontName = WatermarkFontName; + Chart.WatermarkFontSize = WatermarkFontSize; + Chart.WatermarkColor = WatermarkColor; + Chart.WatermarkOpacity = WatermarkOpacity; + Chart.WatermarkRotation = WatermarkRotation; + + // Set file directory save path + Chart.OutputFolderPath = OutputFolderPath; + + Chart.Format = Format; + SignalChart mySignalChart = new SignalChart(); + WriteObject(mySignalChart.Chart(Values, Labels, XOffset, Period, DateTimeTicksBottom, Filename, Width, Height, ScatterXValues)); + } + else + { + ThrowTerminatingError(new ErrorRecord( + new ArgumentNullException(nameof(Values), "Values parameter cannot be null or empty."), + "ValuesNullOrEmpty", + ErrorCategory.InvalidArgument, + nameof(Values))); + } + } + } +} diff --git a/Sources/PowerShell/StackedBarChartPwsh.cs b/Sources/PowerShell/StackedBarChartPwsh.cs index 099dbba..94c5ca3 100644 --- a/Sources/PowerShell/StackedBarChartPwsh.cs +++ b/Sources/PowerShell/StackedBarChartPwsh.cs @@ -21,7 +21,7 @@ public class NewStackedBarChartCommand : Cmdlet [Parameter(Mandatory = true, HelpMessage = "Provide an array of strings for the labels of each bar in the chart.")] public string[] Labels { get; set; } - [Parameter(Mandatory = true, HelpMessage = "Povide an array of strings for the legend categories in the chart.")] + [Parameter(Mandatory = true, HelpMessage = "Provide an array of strings for the legend categories in the chart.")] public string[] LegendCategories { get; set; } // Title settings @@ -125,6 +125,37 @@ public class NewStackedBarChartCommand : Cmdlet [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; + // Set chart Size WxH [Parameter(Mandatory = false, HelpMessage = "Set the width of the chart in pixels.")] public int Width { get; set; } = 400; @@ -138,6 +169,7 @@ public class NewStackedBarChartCommand : Cmdlet protected override void ProcessRecord() { + Chart.Reset(); if (Values != null && Labels != null && LegendCategories != null) { @@ -177,7 +209,7 @@ protected override void ProcessRecord() } else { - throw new Exception("EnableCustomColorPalette requires CustomColorPalette to be set."); + throw new InvalidOperationException("EnableCustomColorPalette requires CustomColorPalette to be set."); } } else @@ -213,6 +245,20 @@ protected override void ProcessRecord() Chart.AxesMarginsLeft = AxesMarginsLeft; Chart.AxesMarginsRight = AxesMarginsRight; + // Background color settings + Chart.FigureBackgroundColor = FigureBackgroundColor; + Chart.DataBackgroundColor = DataBackgroundColor; + + // Watermark settings + Chart.EnableWatermark = EnableWatermark; + Chart.WatermarkText = WatermarkText; + Chart.WatermarkAlignment = WatermarkAlignment; + Chart.WatermarkFontName = WatermarkFontName; + Chart.WatermarkFontSize = WatermarkFontSize; + Chart.WatermarkColor = WatermarkColor; + Chart.WatermarkOpacity = WatermarkOpacity; + Chart.WatermarkRotation = WatermarkRotation; + // Set file directory save path Chart.OutputFolderPath = OutputFolderPath; diff --git a/Sources/SignalChart.cs b/Sources/SignalChart.cs new file mode 100644 index 0000000..64e2192 --- /dev/null +++ b/Sources/SignalChart.cs @@ -0,0 +1,184 @@ +using ScottPlot; +using System; +using System.Collections.Generic; +using System.IO; +using AsBuiltReportChart.Enums; + +namespace AsBuiltReportChart +{ + internal class SignalChart : Chart + { + public object Chart(List values, string[] labels, double xOffset = 0, double period = 1.0, bool dateTimeTicksBottom = false, string filename = "output", int width = 400, int height = 300, List xValues = null) + { + if (values == null || values.Count == 0) + { + throw new ArgumentException("Error: Values cannot be null or empty."); + } + + if (xValues != null) + { + if (xValues.Count != values.Count) + { + throw new ArgumentException("Error: XValues and Values must have the same number of arrays."); + } + + for (int i = 0; i < xValues.Count; i++) + { + if (xValues[i].Length != values[i].Length) + { + throw new ArgumentException($"Error: XValues and Values at index {i} must have the same number of elements."); + } + } + } + + 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; + } + } + + // Add each signal or scatter line + for (int i = 0; i < values.Count; i++) + { + if (xValues != null) + { + // Scatter mode: explicit X and Y values + var scatter = myPlot.Add.Scatter(xValues[i], values[i]); + + if (colorPalette != null) + { + scatter.Color = colorPalette.GetColor(i); + } + + if (labels != null && i < labels.Length && EnableLegend) + { + scatter.LegendText = labels[i]; + } + } + else + { + // Signal mode: Y values only with uniform X spacing + var sig = myPlot.Add.Signal(values[i]); + sig.Data.XOffset = xOffset; + sig.Data.Period = period; + + if (colorPalette != null) + { + sig.Color = colorPalette.GetColor(i); + } + + if (labels != null && i < labels.Length && EnableLegend) + { + sig.LegendText = labels[i]; + } + } + } + + // Enable DateTime ticks on the X axis if requested + if (dateTimeTicksBottom) + { + myPlot.Axes.DateTimeTicksBottom(); + } + + // Set X and Y axis label settings + myPlot.Axes.Bottom.Label.Text = LabelXAxis; + 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; + + myPlot.Axes.Left.Label.Text = LabelYAxis; + 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; + + // Hide unnecessary plot components + myPlot.HideGrid(); + myPlot.Axes.Top.IsVisible = false; + myPlot.Axes.Right.IsVisible = false; + + if (EnableLegend) + { + myPlot.ShowLegend(); + + // Legend Font Properties + myPlot.Legend.FontName = FontName; + myPlot.Legend.FontSize = LegendFontSize; + myPlot.Legend.FontColor = GetDrawingColor(LegendFontColor); + + // Legend box Style Properties + myPlot.Legend.OutlineColor = GetDrawingColor(LegendBorderColor); + myPlot.Legend.OutlineWidth = LegendBorderSize; + myPlot.Legend.OutlinePattern = LegendBorderStyleMap[LegendBorderStyle]; + myPlot.Legend.Orientation = LegendOrientationMap[LegendOrientation]; + myPlot.Legend.Alignment = LegendAlignmentMap[LegendAlignment]; + } + + 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 settings + 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); + } + + // Apply watermark if enabled + ApplyWatermark(myPlot); + + // Set filepath + string Filepath = _outputFolderPath ?? Directory.GetCurrentDirectory(); + + // Save Plot + return SaveInFormat(myPlot, width, height, Filepath, filename, Format); + } + } +} diff --git a/Sources/StackedBarChart.cs b/Sources/StackedBarChart.cs index 20e945e..47f4968 100755 --- a/Sources/StackedBarChart.cs +++ b/Sources/StackedBarChart.cs @@ -10,230 +10,306 @@ internal class StackedBar : Chart static StackedBar() { } public object Chart(List values, string[] labels, string[] categoryNames, string filename = "output", int width = 400, int height = 300) { - if (values.Count != categoryNames.Length) - { - throw new Exception("Error: Values and category names must be equal."); - } - if (values.Count == labels.Length) - { - Plot myPlot = new Plot(); + Plot myPlot = new Plot(); - if (EnableCustomColorPalette) + if (EnableCustomColorPalette) + { + if (_customColorPalette != null && _customColorPalette.Length > 0) { - if (_customColorPalette != null && _customColorPalette.Length > 0) - { - myPlot.Add.Palette = colorPalette = new ScottPlot.Palettes.Custom(_customColorPalette); - } - else - { - throw new Exception("CustomColorPalette is empty. Please provide valid color values."); - } + myPlot.Add.Palette = colorPalette = new ScottPlot.Palettes.Custom(_customColorPalette); } else { - // Set ScottPlot native color palette - if (colorPalette != null) - { - myPlot.Add.Palette = colorPalette; - } + throw new InvalidOperationException("CustomColorPalette is empty. Please provide valid color values."); } - - // Set X and Y axis label settings - if (AreaOrientation == Orientations.Horizontal) - { - myPlot.Axes.Bottom.Label.Text = LabelYAxis; - } - else if (AreaOrientation == Orientations.Vertical) - { - myPlot.Axes.Bottom.Label.Text = LabelXAxis; - } - else + } + else + { + // Set ScottPlot native color palette + if (colorPalette != null) { - myPlot.Axes.Bottom.Label.Text = ""; + myPlot.Add.Palette = colorPalette; } - myPlot.Axes.Bottom.Label.FontSize = LabelFontSize; - myPlot.Axes.Bottom.Label.ForeColor = GetDrawingColor(LabelFontColor); - myPlot.Axes.Bottom.Label.FontName = FontName; + } - myPlot.Axes.Bottom.TickLabelStyle.FontSize = LabelFontSize; - myPlot.Axes.Bottom.TickLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); - myPlot.Axes.Bottom.TickLabelStyle.FontName = FontName; - myPlot.Axes.Bottom.Label.Bold = LabelBold; + // Set X and Y axis label settings + 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.TickLabelStyle.Rotation = -10; + myPlot.Axes.Bottom.TickLabelStyle.FontSize = LabelFontSize; + myPlot.Axes.Bottom.TickLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); + myPlot.Axes.Bottom.TickLabelStyle.FontName = FontName; + myPlot.Axes.Bottom.Label.Bold = LabelBold; - if (AreaOrientation == Orientations.Horizontal) - { - myPlot.Axes.Left.Label.Text = LabelXAxis; - } - else if (AreaOrientation == Orientations.Vertical) - { - myPlot.Axes.Left.Label.Text = LabelYAxis; - } - else + // myPlot.Axes.Bottom.TickLabelStyle.Rotation = -10; + + 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; + + // create bars + var bars = new List(); + // assign values and colors to each bar + // Validate that values.Length matches labels.Length + // This validation is necessary to ensure that each set of values corresponds to a label, which is crucial for accurate representation in the stacked bar chart. If the lengths do not match, it indicates a mismatch in the data structure, leading to potential errors in plotting and misinterpretation of the chart. + if (values.Count != labels.Length) + { + throw new ArgumentException("Error: Value sets and Label length must be equal."); + } + // Validate that each set of values has the same length as category names + // This validation ensures that each category in the stacked bar chart has a corresponding value for each label. If the lengths do not match, it indicates an inconsistency in the data structure, which can lead to errors in plotting and misrepresentation of the chart. Each set of values must align with the category names to accurately reflect the data in the stacked bar chart. + foreach (var valueSet in values) + { + if (valueSet.Length != categoryNames.Length) { - myPlot.Axes.Left.Label.Text = ""; + throw new ArgumentException("Error: Each set of values must have the same length as category names."); } - 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; - - // create bars - var bars = new List(); - // assign values and colors to each bar - for (int x = 0; x < values.Count; x++) + } + + for (int x = 0; x < values.Count; x++) + { + double nextBarBase = 0; + for (int i = 0; i < values[x].Length; i++) { - double nextBarBase = 0; - for (int i = 0; i < values[x].Length; i++) + if (colorPalette != null) { - if (colorPalette != null) + bars.Add(new ScottPlot.Bar { - bars.Add(new ScottPlot.Bar - { - Position = x, - Value = nextBarBase + values[x][i], - ValueBase = nextBarBase, - FillColor = colorPalette.GetColor(i), - Label = $"{values[x][i]}", - CenterLabel = true, - }); - nextBarBase += values[x][i]; - } + Position = x, + Value = nextBarBase + values[x][i], + ValueBase = nextBarBase, + FillColor = colorPalette.GetColor(i), + Label = $"{values[x][i]}", + CenterLabel = true, + }); + nextBarBase += values[x][i]; } } + } - // add bars to plot - var bar = myPlot.Add.Bars(bars); + // add bars to plot + var bar = myPlot.Add.Bars(bars); - // Customize bars label style, including color - bar.ValueLabelStyle.FontName = FontName; - bar.ValueLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); - bar.ValueLabelStyle.Bold = LabelBold; - bar.ValueLabelStyle.FontSize = LabelFontSize; + // Customize bars label style, including color + bar.ValueLabelStyle.FontName = FontName; + bar.ValueLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); + bar.ValueLabelStyle.Bold = LabelBold; + bar.ValueLabelStyle.FontSize = LabelFontSize; - // set each slice value to its label - ScottPlot.TickGenerators.NumericManual tickGen = new ScottPlot.TickGenerators.NumericManual(); + // set each slice value to its label + ScottPlot.TickGenerators.NumericManual tickGen = new ScottPlot.TickGenerators.NumericManual(); - // assign labels to each bar - if (AreaOrientation == Orientations.Vertical) + // assign labels to each bar + if (AreaOrientation == Orientations.Vertical) + { + for (var i = 0; i < categoryNames.Length; i++) { - for (var i = 0; i < categoryNames.Length; i++) + if (colorPalette != null && EnableLegend) { - if (colorPalette != null && EnableLegend) + myPlot.Legend.ManualItems.Add(new LegendItem() { - myPlot.Legend.ManualItems.Add(new LegendItem() - { - LabelText = categoryNames[i], - FillColor = colorPalette.GetColor(i) - }); - } + LabelText = categoryNames[i], + FillColor = colorPalette.GetColor(i) + }); } + } - for (var i = 0; i < labels.Length; i++) - { - // set ticks - tickGen.AddMajor(i, labels[i]); - } - myPlot.Axes.Bottom.TickGenerator = tickGen; + for (var i = 0; i < labels.Length; i++) + { + // set ticks + tickGen.AddMajor(i, labels[i]); } - else + myPlot.Axes.Bottom.TickGenerator = tickGen; + } + else + { + for (var i = 0; i < categoryNames.Length; i++) { - for (var i = 0; i < categoryNames.Length; i++) + if (colorPalette != null && EnableLegend) { - if (colorPalette != null && EnableLegend) + myPlot.Legend.ManualItems.Add(new LegendItem() { - myPlot.Legend.ManualItems.Add(new LegendItem() - { - LabelText = categoryNames[i], - FillColor = colorPalette.GetColor(i) - }); - } + LabelText = categoryNames[i], + FillColor = colorPalette.GetColor(i) + }); } + } - for (var i = 0; i < labels.Length; i++) - { - // set ticks - tickGen.AddMajor(i, labels[i]); - } - // set ticks for horizontal orientation - myPlot.Axes.Left.TickGenerator = tickGen; + for (var i = 0; i < labels.Length; i++) + { + // set ticks + tickGen.AddMajor(i, labels[i]); } + // set ticks for horizontal orientation + myPlot.Axes.Left.TickGenerator = tickGen; + } - bar.Horizontal = (AreaOrientation == Orientations.Horizontal); + bar.Horizontal = (AreaOrientation == Orientations.Horizontal); - // hide unnecessary plot components - myPlot.HideGrid(); - myPlot.Axes.Top.IsVisible = false; - myPlot.Axes.Right.IsVisible = false; + // hide unnecessary plot components + myPlot.HideGrid(); + myPlot.Axes.Top.IsVisible = false; + myPlot.Axes.Right.IsVisible = false; - if (EnableLegend) - { - myPlot.ShowLegend(); + if (EnableLegend) + { + myPlot.ShowLegend(); - // Legend Font Properties - myPlot.Legend.FontName = FontName; - myPlot.Legend.FontSize = LegendFontSize; - myPlot.Legend.FontColor = GetDrawingColor(LegendFontColor); + // Legend Font Properties + myPlot.Legend.FontName = FontName; + myPlot.Legend.FontSize = LegendFontSize; + myPlot.Legend.FontColor = GetDrawingColor(LegendFontColor); - // Legend box Style Properties - myPlot.Legend.OutlineColor = GetDrawingColor(LegendBorderColor); - myPlot.Legend.OutlineWidth = LegendBorderSize; + // Legend box Style Properties + myPlot.Legend.OutlineColor = GetDrawingColor(LegendBorderColor); + myPlot.Legend.OutlineWidth = LegendBorderSize; - myPlot.Legend.OutlinePattern = LegendBorderStyleMap[LegendBorderStyle]; + myPlot.Legend.OutlinePattern = LegendBorderStyleMap[LegendBorderStyle]; - myPlot.Legend.Orientation = LegendOrientationMap[LegendOrientation]; + myPlot.Legend.Orientation = LegendOrientationMap[LegendOrientation]; - myPlot.Legend.Alignment = LegendAlignmentMap[LegendAlignment]; - } + myPlot.Legend.Alignment = LegendAlignmentMap[LegendAlignment]; + } + + if (EnableChartBorder) + { + myPlot.FigureBorder = new LineStyle() + { + // Set chart border properties + 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; + } - if (EnableChartBorder) + // Set margins settings + 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 axis limits if there is only one value to prevent auto-scaling issues + if (values.Count == 1) + { + // Compute cumulative sums for the single stacked bar to capture negative and positive extents + double cumulative = 0; + double minSum = 0; + double maxSum = 0; + + if (values[0] != null && values[0].Length > 0) { - myPlot.FigureBorder = new LineStyle() + foreach (double v in values[0]) { - // Set chart border properties - Color = GetDrawingColor(ChartBorderColor), - Width = ChartBorderSize, - Pattern = ChartBorderStyleMap[ChartBorderStyle], - }; + cumulative += v; + if (cumulative < minSum) + { + minSum = cumulative; + } + if (cumulative > maxSum) + { + maxSum = cumulative; + } + } } - // Set title properties - if (Title != null) + // Ensure that zero is included in the value axis and add padding + double minVal = Math.Min(0, minSum); + double maxVal = Math.Max(0, maxSum); + + if (minVal == maxVal) { - 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; + // Degenerate case: all segments sum to the same value (including all zero) + 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; + } + + double xMin, xMax, yMin, yMax; + double barIndex = 0; // single bar at index 0 - // Set margins settings if (AreaOrientation == Orientations.Horizontal) { - myPlot.Axes.Margins(left: AxesMarginsLeft, right: AxesMarginsRight, bottom: AxesMarginsDown, top: AxesMarginsTop); + // Horizontal bars: X is value, Y is category (bar index) + xMin = minVal; + xMax = maxVal; + yMin = barIndex - 0.5; + yMax = barIndex + 0.5; } else { - myPlot.Axes.Margins(left: AxesMarginsLeft, right: AxesMarginsRight, bottom: AxesMarginsDown, top: AxesMarginsTop); + // Vertical bars: X is category (bar index), Y is value + xMin = barIndex - 0.5; + xMax = barIndex + 0.5; + yMin = minVal; + yMax = maxVal; } - // Set filepath + myPlot.Axes.SetLimits(xMin, xMax, yMin, yMax); + } - string Filepath = _outputFolderPath ?? Directory.GetCurrentDirectory(); + // Apply watermark if enabled + ApplyWatermark(myPlot); - // Set filename - return SaveInFormat(myPlot, width, height, Filepath, filename, Format); - } - else - { - throw new ArgumentException("Error: Values and labels must be equal."); - } + // Set filepath + string Filepath = _outputFolderPath ?? Directory.GetCurrentDirectory(); + + // Set filename + return SaveInFormat(myPlot, width, height, Filepath, filename, Format); } } } diff --git a/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 b/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 index 6b29037..d8d92dc 100644 --- a/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 +++ b/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 @@ -17,6 +17,9 @@ Describe 'AsBuiltReport.Chart Exported Functions' { It 'Should export New-StackedBarChart' { Get-Command -Module AsBuiltReport.Chart -Name New-StackedBarChart | Should -Not -BeNullOrEmpty } + It 'Should export New-SignalChart' { + Get-Command -Module AsBuiltReport.Chart -Name New-SignalChart | Should -Not -BeNullOrEmpty + } Context 'New-PieChart' { It 'Should run without error with sample input' { @@ -65,10 +68,231 @@ Describe 'AsBuiltReport.Chart Exported Functions' { { New-StackedBarChart } | Should -Throw } It 'Should throw error for mismatched Values and Labels' { - { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A') -LegendCategories @('X', 'Y') -OutputFolderPath $TestDrive -Format 'png' } | Should -Throw -ExpectedMessage "Error: Values and labels must be equal." + { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A') -LegendCategories @('X', 'Y') -OutputFolderPath $TestDrive -Format 'png' } | Should -Throw -ExpectedMessage "Error: Value sets and Label length must be equal." } It 'Should throw error for mismatched Values and LegendCategories' { - { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X') -Format 'png' -OutputFolderPath $TestDrive } | Should -Throw -ExpectedMessage "Error: Values and category names must be equal." + { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X') -Format 'png' -OutputFolderPath $TestDrive } | Should -Throw -ExpectedMessage "Error: Each set of values must have the same length as category names." + } + It 'Should run without error with a single element (single-bar chart)' { + { New-StackedBarChart -Title 'Test' -Values @(,[double[]]@(1, 2)) -Labels @('A') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should return a file path as output for a single element' { + $result = New-StackedBarChart -Title 'Test' -Values @(,[double[]]@(1, 2)) -Labels @('A') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + } + + 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 + } + It 'Should return a file path as output' { + $result = New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -Format 'png' -OutputFolderPath $TestDrive + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should run without error with multiple signal lines' { + { New-SignalChart -Title 'Test' -Values @(@(1, 2, 3), @(4, 5, 6)) -Labels @('Line1', 'Line2') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should run without error with DateTime ticks' { + $xOffset = (Get-Date '2024-01-01').ToOADate() + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3)) -XOffset $xOffset -Period 1.0 -DateTimeTicksBottom -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should run without error in scatter mode with explicit X values' { + $xValues = @(,[double[]]@(1.0, 2.0, 3.0, 4.0)) + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -ScatterXValues $xValues -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should return a file path as output in scatter mode' { + $xValues = @(,[double[]]@(1.0, 2.0, 3.0, 4.0)) + $result = New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -ScatterXValues $xValues -Format 'png' -OutputFolderPath $TestDrive + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should run without error in scatter mode with DateTime X values' { + $startDate = (Get-Date '2024-01-01').ToOADate() + { + $xArr = [double[]]@($startDate, ($startDate + 1.0), ($startDate + 2.0)) + New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3)) -ScatterXValues @(,$xArr) -DateTimeTicksBottom -Format 'png' -OutputFolderPath $TestDrive + } | Should -Not -Throw + } + It 'Should run without error in scatter mode with multiple lines' { + $xValues = @(@(1.0, 2.0, 3.0), @(1.0, 2.0, 3.0)) + { New-SignalChart -Title 'Test' -Values @(@(1, 2, 3), @(4, 5, 6)) -ScatterXValues $xValues -Labels @('Line1', 'Line2') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should throw error when ScatterXValues count does not match Values count' { + $xValues = @(,[double[]]@(1.0, 2.0, 3.0)) + { New-SignalChart -Title 'Test' -Values @(@(1, 2, 3), @(4, 5, 6)) -ScatterXValues $xValues -Format 'png' -OutputFolderPath $TestDrive } | Should -Throw -ExpectedMessage "Error: XValues and Values must have the same number of arrays." + } + It 'Should throw error when XValues elements count does not match Values elements count' { + $xValues = @(,[double[]]@(1.0, 2.0)) + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3)) -ScatterXValues $xValues -Format 'png' -OutputFolderPath $TestDrive } | Should -Throw -ExpectedMessage "Error: XValues and Values at index 0 must have the same number of elements." + } + It 'Should throw error for missing mandatory parameters' { + { New-SignalChart } | Should -Throw + } + It 'Should throw error when Values is null or empty' { + { New-SignalChart -Title 'Test' -Values $null -Format 'png' -OutputFolderPath $TestDrive } | Should -Throw + } + Context 'ONTAP-NAS day data (Scatter + DateTimeTicksBottom)' { + BeforeAll { + # Simulate ONTAP-NAS day data: 24 hourly data points for multiple NFS metrics + $baseDate = (Get-Date '2024-01-01').ToOADate() + $script:OntapXArr = [double[]](0..23 | ForEach-Object { $baseDate + ($_ / 24.0) }) + $script:OntapNfsRead = [double[]]@(10.2, 12.5, 8.7, 6.3, 5.1, 7.4, 11.8, 15.6, 18.2, 20.1, 22.4, 19.8, 17.3, 16.5, 18.9, 21.2, 23.6, 20.8, 17.4, 14.2, 12.1, 10.5, 9.3, 8.8) + $script:OntapNfsWrite = [double[]]@(5.1, 6.2, 4.3, 3.1, 2.5, 3.7, 5.9, 7.8, 9.1, 10.0, 11.2, 9.9, 8.6, 8.2, 9.4, 10.6, 11.8, 10.4, 8.7, 7.1, 6.0, 5.2, 4.6, 4.4) + } + It 'Should run without error with ONTAP-NAS day data in scatter mode with DateTimeTicksBottom' { + { + New-SignalChart ` + -Title 'ONTAP-NAS NFS Throughput (Day)' ` + -Values @($script:OntapNfsRead, $script:OntapNfsWrite) ` + -ScatterXValues @($script:OntapXArr, $script:OntapXArr) ` + -Labels @('NFS Read', 'NFS Write') ` + -LabelXAxis 'Time' ` + -LabelYAxis 'Throughput (MB/s)' ` + -DateTimeTicksBottom ` + -EnableLegend ` + -Format 'png' ` + -OutputFolderPath $TestDrive + } | Should -Not -Throw + } + It 'Should return a file path for ONTAP-NAS day data chart' { + $result = New-SignalChart ` + -Title 'ONTAP-NAS NFS Throughput (Day)' ` + -Values @($script:OntapNfsRead, $script:OntapNfsWrite) ` + -ScatterXValues @($script:OntapXArr, $script:OntapXArr) ` + -Labels @('NFS Read', 'NFS Write') ` + -LabelXAxis 'Time' ` + -LabelYAxis 'Throughput (MB/s)' ` + -DateTimeTicksBottom ` + -EnableLegend ` + -Format 'png' ` + -OutputFolderPath $TestDrive + + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + } + } + + Context 'Watermark support' { + Context 'New-PieChart with watermark' { + It 'Should run without error with watermark enabled using defaults' { + { New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark } | Should -Not -Throw + } + It 'Should return a file when watermark is enabled' { + $result = New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should run without error with custom watermark text and color' { + { New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark -WatermarkText 'Confidential' -WatermarkColor Red } | Should -Not -Throw + } + It 'Should run without error with custom watermark font and size' { + { New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark -WatermarkFontSize 36 -WatermarkFontName 'Arial' } | Should -Not -Throw + } + It 'Should run without error with custom watermark opacity' { + { New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark -WatermarkOpacity 0.5 } | Should -Not -Throw + } + It 'Should run without error without watermark (disabled by default)' { + { New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + } + + Context 'New-BarChart with watermark' { + It 'Should run without error with watermark enabled using defaults' { + { New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark } | Should -Not -Throw + } + It 'Should return a file when watermark is enabled' { + $result = New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should run without error with custom watermark color and opacity' { + { New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark -WatermarkColor Blue -WatermarkOpacity 0.2 } | Should -Not -Throw + } + It 'Should run without error without watermark (disabled by default)' { + { New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + } + + Context 'New-StackedBarChart with watermark' { + It 'Should run without error with watermark enabled using defaults' { + { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark } | Should -Not -Throw + } + It 'Should return a file when watermark is enabled' { + $result = New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should run without error with custom watermark font and size' { + { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark -WatermarkFontSize 18 -WatermarkText 'Draft' } | Should -Not -Throw + } + It 'Should run without error without watermark (disabled by default)' { + { New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + } + + Context 'New-SignalChart with watermark' { + It 'Should run without error with watermark enabled using defaults' { + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark } | Should -Not -Throw + } + It 'Should return a file when watermark is enabled' { + $result = New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should run without error with custom watermark color and opacity' { + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark -WatermarkColor Green -WatermarkOpacity 0.4 } | Should -Not -Throw + } + It 'Should run without error without watermark (disabled by default)' { + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + } + } + + Context 'Output format file extensions' { + It 'New-PieChart with Format jpg should produce a .jpg file' { + $result = New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'jpg' -OutputFolderPath $TestDrive + $result.Extension | Should -Be '.jpg' + Test-Path $result | Should -BeTrue + } + It 'New-PieChart with Format jpeg should produce a .jpeg file' { + $result = New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'jpeg' -OutputFolderPath $TestDrive + $result.Extension | Should -Be '.jpeg' + Test-Path $result | Should -BeTrue + } + It 'New-BarChart with Format jpg should produce a .jpg file' { + $result = New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'jpg' -OutputFolderPath $TestDrive + $result.Extension | Should -Be '.jpg' + Test-Path $result | Should -BeTrue + } + It 'New-BarChart with Format jpeg should produce a .jpeg file' { + $result = New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'jpeg' -OutputFolderPath $TestDrive + $result.Extension | Should -Be '.jpeg' + Test-Path $result | Should -BeTrue + } + } + + Context 'State isolation between cmdlet calls' { + It 'EnableLegend should not persist from one New-PieChart call to the next' { + # First call with legend enabled + New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableLegend | Out-Null + # Second call without legend - should not throw and should succeed + { New-PieChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'EnableChartBorder should not persist from one New-BarChart call to the next' { + New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive -EnableChartBorder | Out-Null + { New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'EnableLegend should not persist from New-StackedBarChart to New-BarChart' { + New-StackedBarChart -Title 'Test' -Values @(@(1, 2), @(3, 4)) -Labels @('A', 'B') -LegendCategories @('X', 'Y') -Format 'png' -OutputFolderPath $TestDrive -EnableLegend | Out-Null + { New-BarChart -Title 'Test' -Values @(1, 2) -Labels @('A', 'B') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'EnableWatermark should not persist from one New-SignalChart call to the next' { + New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3)) -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark | Out-Null + { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3)) -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw } } } diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Todo.md @@ -0,0 +1 @@ +