Merge pull request #1219 from jhoneill/PivotTableUpdates

Pivot table updates
This commit is contained in:
Doug Finke
2022-07-16 13:25:17 -04:00
committed by GitHub
11 changed files with 251 additions and 9 deletions

133
Add-Subtotals.ps1 Normal file
View File

@@ -0,0 +1,133 @@
Function Add-Subtotals {
param(
[Parameter(Mandatory=$true, Position=0)]
$ChangeColumnName , # = "Location"
[Parameter(Mandatory=$true, Position=1)]
[hashtable]$AggregateColumn , #= @{"Sales" = "SUM" }
[Parameter(Position=2)]
$ExcelPath = ([System.IO.Path]::GetTempFileName() -replace "\.tmp", ".xlsx") ,
[Parameter(Position=3)]
$WorksheetName = "Sheet1",
[Parameter(ValueFromPipeline=$true)]
$InputObject, #$DataToPivot | Sort location, product
[switch]$HideSingleRows,
[switch]$NoSort,
[switch]$NoOutLine,
[switch]$Show
)
begin {
if (-not $PSBoundParameters.ContainsKey('ExcelPath')) {$Show = $true}
$data = @()
$aggFunctions = [ordered]@{
"AVERAGE" = 1; "COUNT" = 2; "COUNTA" = 3 #(non empty cells) f
"MAX" = 4; "MIN" = 5; "PRODUCT" = 6; "STDEV" = 7 # (sample)
"STDEVP" = 8 # (whole population);
"SUM" = 9; "VAR" = 10 # (Variance sample)
"VARP" = 11 # (whole population) #add 100 to ignore hidden cells
}
}
process {
$data += $InputObject
}
end {
if (-not $NoSort) {$data = $data | Sort-Object $changeColumnName}
$Header = $data[0].PSObject.Properties.Name
#region turn each entry in $AggregateColumn "=SUBTOTAL(a,x{0}}:x{1})" where a is the aggregate function number and x is the column letter
$aggFormulas = @{}
foreach ($k in $AggregateColumn.Keys) {
$columnNo = 0 ;
while ($columnNo -lt $header.count -and $header[$columnNo] -ne $k) {$columnNo ++}
if ($columnNo -eq $header.count) {
throw "'$k' isn't a property of the first row of data."; return
}
if ($AggregateColumn[$k] -is [string]) {
$aggfn = $aggFunctions[$AggregateColumn[$k]]
if (-not $aggfn) {
throw "$($AggregateColumn[$k]) is not a valid aggregation function - these are $($aggFunctions.keys -join ', ')" ; return
}
}
else {$aggfn = $AggregateColumn[$k]}
$aggFormulas[$k] = "=SUBTOTAL({0},{1}{{0}}:{1}{{1}})" -f $aggfn , (Get-ExcelColumnName ($columnNo+1) ).ColumnName
}
if ($aggformulas.count -lt 1) {throw "We didn't get any aggregation formulas"}
$aggFormulas | out-string -Stream | Write-Verbose -Verbose
#endregion
$insertedRows = @()
$singleRows = @()
$previousValue = $data[0].$changeColumnName
$currentRow = $lastChangeRow = 2
#region insert subtotals and send to excel:
#each time there is a change in the column we're intetersted in.
#either Add a row with the value and subtotal(s) function(s) if there is more than one row to total
#or note the row if there was only one row with that value (we may hide it later.)
$excel = $data |
ForEach-Object -process {
if ($_.$changeColumnName -ne $previousValue) {
if ($lastChangeRow -lt ($currentrow - 1)) {
$NewObj = @{$changeColumnName = $previousValue}
foreach ($k in $aggFormulas.Keys) {
$newobj[$k] = $aggformulas[$k] -f $lastChangeRow, ($currentRow - 1)
}
$insertedRows += $currentRow
[pscustomobject]$newobj
$currentRow += 1
}
else {$singleRows += $currentRow }
$lastChangeRow = $currentRow
$previousValue = $_.$changeColumnName
}
$_
$currentRow += 1
} -end { # the process block won't output the last row
if ($lastChangeRow -lt ($currentrow - 1)) {
$NewObj = @{$changeColumnName = $previousValue}
foreach ($k in $aggFormulas.Keys) {
$newobj[$k] = $aggformulas[$k] -f $lastChangeRow, ($currentRow - 1)
}
$insertedRows += $currentRow
[pscustomobject]$newobj
}
else {$singleRows += $currentRow }
} | Export-Excel -Path $ExcelPath -PassThru -AutoSize -AutoFilter -AutoNameRange -BoldTopRow -WorksheetName $WorksheetName -Activate -ClearSheet #-MaxAutoSizeRows 10000
#endregion
#Put the subtotal rows in bold optionally hide rows where only one has the value of interest.
$ws = $excel.$WorksheetName
#We kept lists of the total rows Since 1 rows won't get expand/collapse we can hide them.
foreach ($r in $insertedrows) {$ws.Row($r).style.font.bold = $true }
if ($HideSingleRows) {
foreach ($r in $hideRows) { $ws.Row($r).hidden = $true}
}
$range = $ws.Dimension.Address
$ExcelPath = $excel.File.FullName
$SheetIndex = $ws.index
if ($NoOutline) {
Close-ExcelPackage $excel -show:$Show
return
}
else {
Close-ExcelPackage $excel
try { $excelApp = New-Object -ComObject "Excel.Application" }
catch { Write-Warning "Could not start Excel application - which usually means it is not installed." ; return }
try { $excelWorkBook = $excelApp.Workbooks.Open($ExcelPath) }
catch { Write-Warning -Message "Could not Open $ExcelPath." ; return }
$ws = $excelWorkBook.Worksheets.item($SheetIndex)
$null = $ws.Range($range).Select()
$null = $excelapp.ActiveCell.AutoOutline()
$null = $ws.Outline.ShowLevels(1,$null)
$excelWorkBook.Save()
if ($show) {$excelApp.Visible = $true}
else {
[void]$excelWorkBook.close()
$excelapp.Quit()
}
}
}
}

View File

@@ -6,15 +6,16 @@
#> #>
param( param(
[Parameter(Mandatory=$true)] $moduleName = "ImportExcel",
$moduleName,
[ValidateSet('Column','Bar','Line','Pie')] [ValidateSet('Column','Bar','Line','Pie')]
$chartType="Line" $chartType="Line"
) )
$galleryUrl = "https://www.powershellgallery.com/packages/$moduleName" $download = Get-HtmlTable "https://www.powershellgallery.com/packages/$moduleName" -FirstDataRow 1 |
$nolegend = '-nolegend' Select-Object @{n="Version";e={$v = $Null ; if ($_.version -is [valuetype]) {[string][version]($_.version.tostring("0.0")) }
if($chartType -eq 'pie') {$nolegend = $null} elseif ($_.version -is [string] -and [version]::TryParse($_.version.trim(),[ref]$v)) {$v}
$code = "$($chartType)Chart (Get-HtmlTable $galleryUrl -FirstDataRow 1 | sort lastupdated -desc) -title 'Download stats for $moduleName' $nolegend" else {$_.Version.trim() -replace "\s+"," " } }},
Downloads, @{n="LastUpdated";e={[datetime]$_.last_updated}} |
Sort-Object lastupdated -Descending
$code | Invoke-Expression & "$($chartType)Chart" $download "Download stats for $moduleName" -nolegend:($chartype -ne 'pie')

View File

@@ -0,0 +1,13 @@
try {Import-Module $PSScriptRoot\..\..\ImportExcel.psd1} catch {throw ; return}
#Get rid of pre-exisiting sheet
$xlSourcefile = "$env:TEMP\ImportExcelExample.xlsx"
Write-Verbose -Verbose -Message "Save location: $xlSourcefile"
Remove-Item $xlSourcefile -ErrorAction Ignore
$PivotTableDefinition = New-PivotTableDefinition -Activate -PivotTableName Points `
-PivotRows Driver -PivotColumns Date -PivotData @{Points = "SUM"} -GroupDateColumn Date -GroupDatePart Years, Months
Import-Csv "$PSScriptRoot\First10Races.csv" |
Select-Object Race, @{n = "Date"; e = {[datetime]::ParseExact($_.date, "dd/MM/yyyy", (Get-Culture))}}, FinishPosition, Driver, GridPosition, Team, Points |
Export-Excel $xlSourcefile -Show -AutoSize -PivotTableDefinition $PivotTableDefinition

View File

@@ -0,0 +1,13 @@
try {Import-Module $PSScriptRoot\..\..\ImportExcel.psd1} catch {throw ; return}
#Get rid of pre-exisiting sheet
$xlSourcefile = "$env:TEMP\ImportExcelExample.xlsx"
Write-Verbose -Verbose -Message "Save location: $xlSourcefile"
Remove-Item $xlSourcefile -ErrorAction Ignore
$PivotTableDefinition = New-PivotTableDefinition -Activate -PivotTableName Places `
-PivotRows Driver -PivotColumns FinishPosition -PivotData @{Date = "Count"} -GroupNumericColumn FinishPosition -GroupNumericMin 1 -GroupNumericMax 25 -GroupNumericInterval 3
Import-Csv "$PSScriptRoot\First10Races.csv" |
Select-Object Race, @{n = "Date"; e = {[datetime]::ParseExact($_.date, "dd/MM/yyyy", (Get-Culture))}}, FinishPosition, Driver, GridPosition, Team, Points |
Export-Excel $xlSourcefile -Show -AutoSize -PivotTableDefinition $PivotTableDefinition

View File

@@ -18,8 +18,10 @@
[String]$PivotTotals = "Both", [String]$PivotTotals = "Both",
[Switch]$NoTotalsInPivot, [Switch]$NoTotalsInPivot,
[String]$GroupDateRow, [String]$GroupDateRow,
[String]$GroupDateColumn,
[OfficeOpenXml.Table.PivotTable.eDateGroupBy[]]$GroupDatePart, [OfficeOpenXml.Table.PivotTable.eDateGroupBy[]]$GroupDatePart,
[String]$GroupNumericRow, [String]$GroupNumericRow,
[String]$GroupNumericColumn,
[double]$GroupNumericMin = 0 , [double]$GroupNumericMin = 0 ,
[double]$GroupNumericMax = [Double]::MaxValue , [double]$GroupNumericMax = [Double]::MaxValue ,
[double]$GroupNumericInterval = 100 , [double]$GroupNumericInterval = 100 ,
@@ -139,11 +141,21 @@
if (-not $r ) {Write-Warning -Message "Could not find a Row field named '$GroupNumericRow'; no numeric grouping will be done."} if (-not $r ) {Write-Warning -Message "Could not find a Row field named '$GroupNumericRow'; no numeric grouping will be done."}
else {$r.AddNumericGrouping($GroupNumericMin, $GroupNumericMax, $GroupNumericInterval)} else {$r.AddNumericGrouping($GroupNumericMin, $GroupNumericMax, $GroupNumericInterval)}
} }
elseif ($GroupNumericColumn) {
$c = $pivotTable.ColumnFields.Where( {$_.name -eq $GroupNumericColumn })
if (-not $c ) {Write-Warning -Message "Could not find a Column field named '$GroupNumericColumn'; no numeric grouping will be done."}
else {$c.AddNumericGrouping($GroupNumericMin, $GroupNumericMax, $GroupNumericInterval)}
}
if ($GroupDateRow -and $PSBoundParameters.ContainsKey("GroupDatePart")) { if ($GroupDateRow -and $PSBoundParameters.ContainsKey("GroupDatePart")) {
$r = $pivotTable.RowFields.Where( {$_.name -eq $GroupDateRow }) $r = $pivotTable.RowFields.Where( {$_.name -eq $GroupDateRow })
if (-not $r ) {Write-Warning -Message "Could not find a Row field named '$GroupDateRow'; no date grouping will be done."} if (-not $r ) {Write-Warning -Message "Could not find a Row field named '$GroupDateRow'; no date grouping will be done."}
else {$r.AddDateGrouping($GroupDatePart)} else {$r.AddDateGrouping($GroupDatePart)}
} }
elseif ($GroupDateColumn -and $PSBoundParameters.ContainsKey("GroupDatePart")) {
$c = $pivotTable.ColumnFields.Where( {$_.name -eq $GroupDateColumn })
if (-not $c ) {Write-Warning -Message "Could not find a Column field named '$GroupDateColumn'; no date grouping will be done."}
else {$c.AddDateGrouping($GroupDatePart)}
}
} }
catch {Write-Warning -Message "Failed adding PivotTable '$pivotTableName': $_"} catch {Write-Warning -Message "Failed adding PivotTable '$pivotTableName': $_"}
} }

View File

@@ -16,8 +16,10 @@ function New-PivotTableDefinition {
[String]$PivotTotals = "Both", [String]$PivotTotals = "Both",
[Switch]$NoTotalsInPivot, [Switch]$NoTotalsInPivot,
[String]$GroupDateRow, [String]$GroupDateRow,
[String]$GroupDateColumn,
[OfficeOpenXml.Table.PivotTable.eDateGroupBy[]]$GroupDatePart, [OfficeOpenXml.Table.PivotTable.eDateGroupBy[]]$GroupDatePart,
[String]$GroupNumericRow, [String]$GroupNumericRow,
[String]$GroupNumericColumn,
[double]$GroupNumericMin = 0 , [double]$GroupNumericMin = 0 ,
[double]$GroupNumericMax = [Double]::MaxValue , [double]$GroupNumericMax = [Double]::MaxValue ,
[double]$GroupNumericInterval = 100 , [double]$GroupNumericInterval = 100 ,

View File

@@ -65,6 +65,11 @@ Plus, wiring the [PowerShell ScriptAnalyzer Excel report](https://github.com/dfi
![](.gitbook/assets/ScriptAnalyzerReport.png) ![](.gitbook/assets/ScriptAnalyzerReport.png)
## What's new for 7.1.4
- Added GroupNumericColumn and GroupDateColumn to New-PivotTableDefinition and Add-PivotTable.
## What's new 7.1.3 ## What's new 7.1.3
- Changed to `ProviderPath`. Thanks [Trevor Walker](https://github.com/sporkabob) - Changed to `ProviderPath`. Thanks [Trevor Walker](https://github.com/sporkabob)

View File

@@ -331,6 +331,22 @@ Accept pipeline input: False
Accept wildcard characters: False Accept wildcard characters: False
``` ```
### -GroupDateColumn
The name of a Column field which should be grouped by parts of the date/time \(ignored if GroupDateRow is not specified\)
```yaml
Type: String
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -GroupDatePart ### -GroupDatePart
The Part\(s\) of the date to use in the grouping \(ignored if GroupDateRow is not specified\) The Part\(s\) of the date to use in the grouping \(ignored if GroupDateRow is not specified\)
@@ -364,6 +380,22 @@ Accept pipeline input: False
Accept wildcard characters: False Accept wildcard characters: False
``` ```
### -GroupNumericColumn
The name of a Column field which should be grouped by Number \(e.g. 0-99, 100-199, 200-299 \)
```yaml
Type: String
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -GroupNumericMin ### -GroupNumericMin
The starting point for grouping The starting point for grouping

View File

@@ -225,6 +225,22 @@ Accept pipeline input: False
Accept wildcard characters: False Accept wildcard characters: False
``` ```
### -GroupDateColumn
The name of a column field which should be grouped by parts of the date/time \(ignored if GroupDatePart is not specified\)
```yaml
Type: String
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -GroupDatePart ### -GroupDatePart
The Part\(s\) of the date to use in the grouping \(ignored if GroupDateRow is not specified\) The Part\(s\) of the date to use in the grouping \(ignored if GroupDateRow is not specified\)
@@ -234,7 +250,6 @@ Type: eDateGroupBy[]
Parameter Sets: (All) Parameter Sets: (All)
Aliases: Aliases:
Accepted values: Years, Quarters, Months, Days, Hours, Minutes, Seconds Accepted values: Years, Quarters, Months, Days, Hours, Minutes, Seconds
Required: False Required: False
Position: Named Position: Named
Default value: None Default value: None
@@ -250,7 +265,6 @@ The name of a row field which should be grouped by Number \(e.g 0-99, 100-199, 2
Type: String Type: String
Parameter Sets: (All) Parameter Sets: (All)
Aliases: Aliases:
Required: False Required: False
Position: Named Position: Named
Default value: None Default value: None
@@ -258,6 +272,23 @@ Accept pipeline input: False
Accept wildcard characters: False Accept wildcard characters: False
``` ```
### -GroupNumericColumn
The name of a column field which should be grouped by Number \(e.g 0-99, 100-199, 200-299 \)
```yaml
Type: String
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -GroupNumericMin ### -GroupNumericMin
The starting point for grouping The starting point for grouping