Data-Formatters.ps1

$LIGHTGREEN = 10416289
$GREEN = 1268766
$LIGHTRED = 10396159
$RED = 2108032
$BLUES = @(10249511, 14058822, 16758932)
$ORANGES = @(294092, 1681916, 6014716)

$EPS = 0.0001

$WorksheetMaxLen = 31

$ABBREVIATIONS = @{
    "sessions" = "sess."
    "bufferLen" = "bufLen."
    "bufferCount" = "bufCt."
    "protocol" = "protocol"
    "sendMethod" = "sndMthd" 
}

$THROUGHPUTS = @(1, 10, 25, 40, 50, 100, 200, 400)


##
# Format-RawData
# --------------
# This function formats raw data into tables, one for each dataEntry property. Data samples are
# organized by their sortProp and labeled with the name of the file from which the data sample was extracted.
#
# Parameters
# ----------
# DataObj (HashTable) - Object containing processed data, raw data, and meta data
# TableTitle (String) - Title to be displayed at the top of each table
#
# Return
# ------
# HashTable[] - Array of HashTable objects which each store a table of formatted raw data
#
##
function Format-RawData {
    param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter(Mandatory=$true)] $OPivotKey,

        [Parameter()] [String] $Tool = "",

        [Parameter()] [switch] $NoNewWorksheets
    )

    $legend = @{
        "meta" = @{
            "colLabelDepth" = 1
            "rowLabelDepth" = 1
            "dataWidth"     = 2
            "dataHeight"    = 3 
        }
        "rows" = @{
            " "   = 0
            " "  = 1
            " " = 2
        }
        "cols" = @{
            "legend" = @{
                " "  = 0
                " " = 1
            }
        }
        "data" = @{
            "legend" = @{
                " " = @{
                    " " = @{
                        "value" = "Test values are compared against the mean basline value."
                    }
                    " " = @{
                        "value" = "Test values which show improvement are colored green:"
                    }
                    " " = @{
                        "value" = "Test values which show regression are colored red:"
                    }
                }
                " " = @{
                    " " = @{
                        "value"     = "Improvement"
                        "fontColor" = $GREEN
                        "cellColor" = $LIGHTGREEN
                    }
                    " " = @{
                        "value"     = "Regression"
                        "fontColor" = $RED
                        "cellColor" = $LIGHTRED
                    }
                } 
            }
        }
    }

    $meta       = $DataObj.meta
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot
    $tables     = @()

    if (-not $NoNewWorksheets) {
        $tables += Get-WorksheetTitle -BaseName "Raw Data" -OuterPivot $outerPivot -OPivotKey $OPivotKey
    }
    if ($meta.comparison) {
        $tables += $legend
    }

    # Fill single array with all data and sort, label data as baseline/test if necessary
    [Array] $data = @() 
    foreach ($entry in $DataObj.rawData.baseline) {
        if ($meta.comparison) {
            $entry.baseline = $true
        } 
        if (($OPivotKey -eq $NoPivot) -or ($entry.$outerPivot -eq $OPivotKey)) {
            $data += $entry
        }
    }

    if ($meta.comparison) {
        foreach ($entry in $DataObj.rawData.test) {
            if (($OPivotKey -eq $NoPivot) -or ($entry.$outerPivot -eq $OPivotKey)) {
                $data += $entry
            }
        }
    }

    if ($innerPivot) {
        $data = Sort-ByProp -Data $data -Prop $innerPivot
    }
    

    foreach ($prop in $dataObj.data.$OPivotKey.Keys) {
        $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey

        $table = @{
            "rows" = @{
                $prop = @{}
            }
            "cols" = @{
                $tableTitle = @{
                    $innerPivot = @{}
                }
            }
            "meta" = @{
                "columnFormats" = @()
                "leftAlign"     = [Array] @(2)
            }
            "data"  = @{
                $tableTitle = @{
                    $innerPivot = @{}
                }
            }
        }
        $col = 0
        $row = 0

        foreach ($entry in $data) {
            $iPivotKey = $NoPivot
            if ($innerPivot) {
                $iPivotKey = $entry.$innerPivot
            }

            # Add column labels to table
            if (-not ($table.cols.$tableTitle.$innerPivot.Keys -contains $iPivotKey)) {
                if ($meta.comparison) {
                    $table.cols.$tableTitle.$innerPivot.$iPivotKey = @{
                        "baseline" = $col
                        "test"     = $col + 1
                    }
                    $table.meta.columnFormats += @($meta.format.$prop, $meta.format.$prop)
                    $col += 2
                    $table.data.$tableTitle.$innerPivot.$iPivotKey = @{
                        "baseline" = @{
                            $prop = @{}
                        }
                        "test" = @{
                            $prop = @{}
                        }
                    }
                } 
                else {
                    $table.meta.columnFormats += $meta.format.$prop
                    $table.cols.$tableTitle.$innerPivot.$iPivotKey = $col
                    $table.data.$tableTitle.$innerPivot.$iPivotKey = @{
                        $prop = @{}
                    }
                    $col += 1
                }
            }

            # Add row labels and fill data in table
            $filename = $entry.fileName.Split('\')[-2] + "\" + $entry.fileName.Split('\')[-1]
            while ($table.rows.$prop.keys -contains $filename) {
                $filename += "*"
            }
            $table.rows.$prop.$filename = $row
            
            $row += 1
            if ($meta.comparison) {
                if ($entry.baseline) {
                    $table.data.$tableTitle.$innerPivot.$iPivotKey.baseline.$prop.$filename = @{
                        "value" = $entry.$prop
                    }
                }
                else {
                    $table.data.$tableTitle.$innerPivot.$iPivotKey.test.$prop.$filename = @{
                        "value" = $entry.$prop
                    }
                    $params = @{
                        "Cell"    = $table.data.$tableTitle.$innerPivot.$iPivotKey.test.$prop.$filename
                        "TestVal" = $entry.$prop
                        "BaseVal" = $DataObj.data.$OPivotKey.$prop.$iPivotKey.baseline.stats.mean
                        "Goal"    = $meta.goal.$prop
                    }
                    
                    $table.data.$tableTitle.$innerPivot.$iPivotKey.test.$prop.$filename = Select-Color @params
                }
            } 
            else {
                $table.data.$tableTitle.$innerPivot.$iPivotKey.$prop.$filename = @{
                    "value" = $entry.$prop
                }
            }
        }
        $table.meta.dataWidth     = Get-TreeWidth $table.cols
        $table.meta.colLabelDepth = Get-TreeDepth $table.cols
        $table.meta.dataHeight    = Get-TreeWidth $table.rows
        $table.meta.rowLabelDepth = Get-TreeDepth $table.rows
        $tables = $tables + $table
    }

    foreach ($entry in $data) {
        if ($entry.baseline) {
            $entry.Remove("baseline")
        }
    }
    return $tables
}

function Get-WorksheetTitle ($BaseName, $OuterPivot, $OPivotKey, $InnerPivot, $IPivotKey) {
    if ($OuterPivot -and $InnerPivot) {
        $OAbv = $ABBREVIATIONS[$OuterPivot]
        $IAbv = $ABBREVIATIONS[$InnerPivot]

        $name = "$BaseName - $OPivotKey $OAbv - $IPivotKey $IAbv"

        if ($name.Length -gt $WorksheetMaxLen) {
            $name = "$BaseName - $OPivotKey - $IPivotKey"
        }

        return $name
    } 
    elseif ($OuterPivot) {
        $OAbv = $ABBREVIATIONS[$OuterPivot]
        $name = "$BaseName - $OPivotKey $OAbv"

        if ($name.Length -gt $WorksheetMaxLen) {
            $name = "$BaseName - $OPivotKey"
        }

        return $name
    } 
    elseif ($InnerPivot) {
        $IAbv = $ABBREVIATIONS[$InnerPivot]
        $name = "$BaseName - $IPivotKey $IAbv"

        if ($name.Length -gt $WorksheetMaxLen) {
            $name = "$BaseName - $IPivotKey"
        }

        return $name 
    }
    else {
        return "$BaseName"
    }
}

function Get-TableTitle ($Tool, $OuterPivot, $OPivotKey, $InnerPivot, $IPivotKey) { 
    if ($OuterPivot -and $InnerPivot) {
        $OAbv = $ABBREVIATIONS[$OuterPivot]
        $IAbv = $ABBREVIATIONS[$InnerPivot]

        return "$Tool - $OPivotKey $OAbv - $IPivotKey $IAbv"
    } 
    elseif ($OuterPivot) {
        $OAbv = $ABBREVIATIONS[$OuterPivot]

        return "$Tool - $OPivotKey $OAbv"
    } 
    elseif ($InnerPivot) {
        $IAbv = $ABBREVIATIONS[$InnerPivot]

        return "$Tool - $IPivotKey $IAbv"
    }
    else {
        return "$Tool"
    }
    
}

##
# Format-Stats
# -------------------
# This function formats statistical metrics (min, mean, max, etc) into a table, one per property.
# When run in comparison mode, the table also displays % change and is color-coded to indicate
# improvement/regression.
#
# Parameters
# ----------
# DataObj (HashTable) - Object containing processed data, raw data, and meta data
# TableTitle (String) - Title to be displayed at the top of each table
# Metrics (String[]) - Array containing statistical metrics that should be displayed on generated
# tables. All metrics are displayed if this parameter is null.
#
# Return
# ------
# HashTable[] - Array of HashTable objects which each store a table of formatted statistical data
#
##
function Format-Stats {
    Param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter(Mandatory=$true)] $OPivotKey,

        [Parameter()] $Metrics = $null,

        [Parameter()] [String] $Tool = "",

        [Parameter()] [switch] $NoNewWorksheets
    )
    
    $tables = @()
    $data = $DataObj.data
    $meta = $DataObj.meta
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot

    foreach ($prop in $data.$OPivotKey.keys) {
        $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey

        $table = @{
            "rows" = @{
                $prop = @{}
            }
            "cols" = @{
                $tableTitle = @{
                    $innerPivot = @{}
                }
            }
            "meta" = @{
                "columnFormats" = @()
            }
            "data" = @{
                $tableTitle = @{
                    $innerPivot = @{}
                }
            }
        }

        $col = 0
        $row = 0
        $noStats = $false
        foreach ($IPivotKey in $data.$OPivotKey.$prop.Keys | Sort) {
            if (-Not ($data.$OPivotKey.$prop.$IPivotKey.baseline.Keys -contains "stats")) {
                $noStats = $true
                break
            }

            # Add column labels to table
            if (-not $meta.comparison) {
                $table.cols.$tableTitle.$innerPivot.$IPivotKey  = $col 
                $table.data.$tableTitle.$innerPivot.$IPivotKey  = @{
                    $prop = @{}
                }
                $col += 1
                $table.meta.columnFormats += $meta.format.$prop
            } 
            else {
                $table.cols.$tableTitle.$innerPivot.$IPivotKey = @{
                    "baseline" = $col
                    "% Change" = $col + 1
                    "test"     = $col + 2
                }
                $table.meta.columnFormats += $meta.format.$prop
                $table.meta.columnFormats += $meta.format."% change"
                $table.meta.columnFormats += $meta.format.$prop
                $col += 3
                $table.data.$tableTitle.$innerPivot.$IPivotKey = @{
                    "baseline" = @{
                        $prop = @{}
                    }
                    "% Change" = @{
                        $prop = @{}
                    }
                    "test" = @{
                        $prop = @{}
                    }
                }
            }

            if (-not $Metrics) {
                $Metrics = ($data.$OPivotKey.$prop.$IPivotKey.baseline.stats.Keys | Sort)
            }

            # Add row labels and fill data in table
            foreach ($metric in $Metrics) {
                if (-not ($table.rows.$prop.Keys -contains $metric)) {
                    $table.rows.$prop.$metric = $row
                    $row += 1
                }

                if (-not $meta.comparison) {
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.$prop.$metric = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.$metric}
                } 
                else {
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.baseline.$prop.$metric = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.$metric}
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.test.$prop.$metric     = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.test.stats.$metric}
                
                    $percentChange = $data.$OPivotKey.$prop.$IPivotKey."% change".stats.$metric

                    $table.data.$tableTitle.$innerPivot.$IPivotKey."% change".$prop.$metric = @{"value" = "$percentChange %"}

                    $params = @{
                        "Cell"    = $table.data.$tableTitle.$innerPivot.$IPivotKey."% change".$prop.$metric
                        "TestVal" = $data.$OPivotKey.$prop.$IPivotKey.test.stats.$metric
                        "BaseVal" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.$metric
                        "Goal"    = $meta.goal.$prop
                    }

                    # Color % change cell if necessary
                    if (@("std dev", "variance", "std err", "range") -contains $metric) {
                        $params.goal = "decrease"
                        $table.data.$tableTitle.$innerPivot.$IPivotKey."% change".$prop.$metric = Select-Color @params
                    } 
                    elseif ( -not (@("sum", "count", "kurtosis", "skewness") -contains $metric)) {
                        $table.data.$tableTitle.$innerPivot.$IPivotKey."% change".$prop.$metric = Select-Color @params
                    }
                }
            }
        }

        if ($noStats) {
            continue
        }

        $table.meta.dataWidth     = Get-TreeWidth $table.cols
        $table.meta.colLabelDepth = Get-TreeDepth $table.cols
        $table.meta.dataHeight    = Get-TreeWidth $table.rows
        $table.meta.rowLabelDepth = Get-TreeDepth $table.rows
        $tables = $tables + $table
    }
    if (($tables.Count -gt 0) -and (-not $NoNewWorksheets)) {
        $sheetTitle = Get-WorksheetTitle -BaseName "Stats" -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $tables     = @($sheetTitle) + $tables 
    }

    return $tables
}


##
# Format-Quartiles
# ----------------
# This function formats a table in order to create a chart that displays the quartiles
# of each data subcategory (organized by sortProp), one chart per property.
#
# Parameters
# ----------
# DataObj (HashTable) - Object containing processed data, raw data, and meta data
# TableTitle (String) - Title to be displayed at the top of each table
#
# Return
# ------
# HashTable[] - Array of HashTable objects which each store a table of formatted quartile data
#
##
function Format-Quartiles {
    param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter(Mandatory=$true)] $OPivotKey, 

        [Parameter()] [String] $Tool = "",

        [Parameter()] [switch] $NoNewWorksheets
    )
    $tables = @()
    $data = $DataObj.data
    $meta = $DataObj.meta
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot

    foreach ($prop in $data.$OPivotKey.Keys) { 
        $format = $meta.format.$prop
        $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $cappedProp = (Get-Culture).TextInfo.ToTitleCase($prop)
        $table = @{
            "rows" = @{
                $prop = @{
                    $innerPivot = @{}
                }
            }
            "cols" = @{
                $tableTitle = @{
                    "min" = 0
                    "Q1"  = 1
                    "Q2"  = 2
                    "Q3"  = 3
                    "Q4"  = 4
                }
            }
            "meta" = @{
                "columnFormats" = @($format, $format, $format, $format, $format )
                "dataWidth" = 5
            }
            "data" = @{
                $tableTitle = @{
                    "min" = @{
                        $prop = @{
                            $innerPivot = @{}
                        }
                    }
                    "Q1" = @{
                        $prop = @{
                            $innerPivot = @{}
                        }
                    }
                    "Q2" = @{
                        $prop = @{
                            $innerPivot = @{}
                        }
                    }
                    "Q3" = @{
                        $prop = @{
                            $innerPivot = @{}
                        }
                    }
                    "Q4" = @{
                        $prop = @{
                            $innerPivot = @{}
                        }
                    }
                }
            }
            "chartSettings" = @{ 
                "chartType"= [Microsoft.Office.Interop.Excel.XlChartType]::xlColumnStacked
                "plotBy"   = [Microsoft.Office.Interop.Excel.Constants]::xlColumns
                "xOffset"  = 1
                "YOffset"  = 1
                "title"    = "$cappedProp Quartiles"
                "seriesSettings"= @{
                    1 = @{
                        "hide" = $true
                        "name" = " "
                    }
                }
                "axisSettings" = @{
                    1 = @{
                        "majorGridlines" = $true
                    }
                    2 = @{
                        "minorGridlines" = $true
                        "title" = $meta.units[$prop]
                    }
                }
            }
        }
    
        
        # Add row labels and fill data in table
        $row = 0
        foreach ($IPivotKey in $data.$OPivotKey.$prop.Keys | Sort) {
            if (-not $meta.comparison) {
                $table.rows.$prop.$innerPivot.$IPivotKey = $row
                $row += 1
                $table.data.$TableTitle.min.$prop.$innerPivot.$IPivotKey = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.min }
                $table.data.$TableTitle.Q1.$prop.$innerPivot.$IPivotKey  = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[25] - $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.min }
                $table.data.$TableTitle.Q2.$prop.$innerPivot.$IPivotKey  = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[50] - $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[25] } 
                $table.data.$TableTitle.Q3.$prop.$innerPivot.$IPivotKey  = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[75] - $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[50]}
                $table.data.$TableTitle.Q4.$prop.$innerPivot.$IPivotKey  = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.max - $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[75] }
            } 
            else {
                $table.rows.$prop.$innerPivot.$IPivotKey = @{
                    "baseline" = $row
                    "test"     = $row + 1
                }
                $row += 2
                $table.data.$TableTitle.min.$prop.$innerPivot.$IPivotKey = @{
                    "baseline" = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.min }
                    "test"     = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.test.stats.min}
                }
                $table.data.$TableTitle.Q1.$prop.$innerPivot.$IPivotKey = @{
                    "baseline" = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[25] - $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.min }
                    "test"     = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.test.percentiles[25] - $data.$OPivotKey.$prop.$IPivotKey.test.stats.min }
                }
                $table.data.$TableTitle.Q2.$prop.$innerPivot.$IPivotKey = @{
                    "baseline" = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[50] - $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[25] } 
                    "test"     = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.test.percentiles[50] - $data.$OPivotKey.$prop.$IPivotKey.test.percentiles[25] } 
                }
                $table.data.$TableTitle.Q3.$prop.$innerPivot.$IPivotKey = @{
                    "baseline" = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[75] - $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[50] } 
                    "test"     = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.test.percentiles[75] - $data.$OPivotKey.$prop.$IPivotKey.test.percentiles[50] }
                }
                $table.data.$TableTitle.Q4.$prop.$innerPivot.$IPivotKey = @{
                    "baseline" = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.max - $data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles[75] }
                    "test"     = @{ "value" = $data.$OPivotKey.$prop.$IPivotKey.test.stats.max - $data.$OPivotKey.$prop.$IPivotKey.test.percentiles[75] }
                }
            }
        }

        $table.meta.dataWidth     = Get-TreeWidth $table.cols
        $table.meta.colLabelDepth = Get-TreeDepth $table.cols
        $table.meta.dataHeight    = Get-TreeWidth $table.rows
        $table.meta.rowLabelDepth = Get-TreeDepth $table.rows
        $tables = $tables + $table
    }

    if (($tables.Count -gt 0) -and (-not $NoNewWorksheets)) {
        $sheetTitle = Get-WorksheetTitle -BaseName "Quartiles" -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $tables = @($sheetTitle) + $tables
    }

    return $tables
}


##
# Format-MinMaxChart
# ----------------
# This function formats a table that displays min, mean, and max of each data subcategory,
# one table per property. This table primarily serves to generate a line chart for the
# visualization of this data.
#
# Parameters
# ----------
# DataObj (HashTable) - Object containing processed data, raw data, and meta data
# TableTitle (String) - Title to be displayed at the top of each table
#
# Return
# ------
# HashTable[] - Array of HashTable objects which each store a table of formatted data
#
##
function Format-MinMaxChart {
    Param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter(Mandatory=$true)] $OPivotKey, 

        [Parameter()] [String] $Tool = "",

        [Parameter()] [switch] $NoNewWorksheets
    )
    
    $tables     = @()
    $data       = $DataObj.data
    $meta       = $DataObj.meta
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot

    foreach ($prop in $data.$OPivotKey.keys) {
        $cappedProp = (Get-Culture).TextInfo.ToTitleCase($prop) 
        $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $table = @{
            "rows" = @{
                $prop = @{}
            }
            "cols" = @{
                $tableTitle = @{
                    $innerPivot = @{}
                }
            }
            "meta" = @{
                "columnFormats" = @()
            }
            "data" = @{
                $tableTitle = @{
                    $innerPivot = @{}
                }
            }
            "chartSettings" = @{
                "chartType"    = [Microsoft.Office.Interop.Excel.XlChartType]::xlLineMarkers
                "plotBy"       = [Microsoft.Office.Interop.Excel.XlRowCol]::xlRows
                "title"        = $cappedProp
                "xOffset"      = 1
                "yOffset"      = 2
                "dataTable"    = $true
                "hideLegend"   = $true
                "axisSettings" = @{
                    1 = @{
                        "majorGridlines" = $true
                    }
                    2 = @{
                        "minorGridlines" = $true
                        "title" = $meta.units.$prop
                    }
                }
            }
        }
        if ($meta.comparison) {
            $table.chartSettings.seriesSettings = @{
                1 = @{
                    "color"       = $BLUES[2]
                    "markerColor" = $BLUES[2]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                2 = @{
                    "color"       = $ORANGES[2]
                    "markerColor" = $ORANGES[2]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                3 = @{
                    "color"       = $BLUES[1]
                    "markerColor" = $BLUES[1]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                4 = @{
                    "color"       = $ORANGES[1]
                    "markerColor" = $ORANGES[1]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                5 = @{
                    "color"       = $BLUES[0]
                    "markerColor" = $BLUES[0]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                6 = @{
                    "color"       = $ORANGES[0]
                    "markerColor" = $ORANGES[0]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
            }
        } 
        else {
            $table.chartSettings.seriesSettings = @{
                1 = @{
                    "color"       = $BLUES[2]
                    "markerColor" = $BLUES[2]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                2 = @{
                    "color"       = $BLUES[1]
                    "markerColor" = $BLUES[1]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
                3 = @{
                    "color"       = $BLUES[0]
                    "markerColor" = $BLUES[0]
                    "markerStyle" = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                    "lineWeight"  = 3
                    "markerSize"  = 5
                }
            }
        }

        if (-not $innerPivot) {
            $table.chartSettings.yOffset = 3
        }

        $col = 0
        $row = 0
        foreach ($IPivotKey in $data.$OPivotKey.$prop.Keys | Sort) {
            # Add column labels to table
            $table.cols.$tableTitle.$innerPivot.$IPivotKey = $col
            $table.data.$tableTitle.$innerPivot.$IPivotKey = @{
                $prop = @{}
            }
            $table.meta.columnFormats += $meta.format.$prop
            $col += 1
        
            # Add row labels and fill data in table
            foreach ($metric in @("min", "mean", "max")) {
                if (-not ($table.rows.$prop.Keys -contains $metric)) { 
                    if (-not $meta.comparison) {
                        $table.rows.$prop.$metric = $row
                        $row += 1
                    } 
                    else {
                        $table.rows.$prop.$metric = @{
                            "baseline" = $row
                            "test"     = $row + 1
                        } 
                        $row += 2
                    }
                }
                if (-not ($table.data.$tableTitle.$innerPivot.$IPivotKey.$prop.Keys -contains $metric)) {
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.$prop.$metric = @{}
                }

                if (-not $meta.comparison) {
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.$prop.$metric = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.$metric}
                } 
                else {
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.$prop.$metric.baseline = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.stats.$metric}
                    $table.data.$tableTitle.$innerPivot.$IPivotKey.$prop.$metric.test     = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.test.stats.$metric}
                }
            }

        }
        $table.meta.dataWidth     = Get-TreeWidth $table.cols
        $table.meta.colLabelDepth = Get-TreeDepth $table.cols
        $table.meta.dataHeight    = Get-TreeWidth $table.rows
        $table.meta.rowLabelDepth = Get-TreeDepth $table.rows
        $tables = $tables + $table
    }

    if (($tables.Count -gt 0) -and (-not $NoNewWorksheets)) {
        $sheetTitle = Get-WorksheetTitle -BaseName "MinMeanMax" -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $tables = @($sheetTitle) + $tables
    }

    return $tables
}


##
# Format-Percentiles
# ----------------
# This function formats a table displaying percentiles of each data subcategory, one
# table per property + sortProp combo. When in comparison mode, percent change is also
# plotted and is color-coded to indicate improvement/regression. A chart is also formatted
# with each table.
#
# Parameters
# ----------
# DataObj (HashTable) - Object containing processed data, raw data, and meta data
# TableTitle (String) - Title to be displayed at the top of each table
#
# Return
# ------
# HashTable[] - Array of HashTable objects which each store a table of formatted percentile data
#
##
function Format-Percentiles {
    Param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter(Mandatory=$true)] $OPivotKey,

        [Parameter()] [String] $Tool = "",

        [Parameter()] [switch] $NoNewWorksheets
    )

    $tables     = @()
    $data       = $DataObj.data
    $meta       = $DataObj.meta
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot

    
    foreach ($prop in $data.$OPivotKey.Keys) {
        foreach ($IPivotKey in $data.$OPivotKey.$prop.Keys | Sort) {
            if ($data.$OPivotKey.$prop.$IPivotKey.baseline.percentiles) {
                $metricName = "percentiles"
            } 
            elseif ($data.$OPivotKey.$prop.$IPivotKey.baseline.percentilesHist) {
                $metricName = "percentilesHist"
            } 
            else {
                continue
            }

            if ($innerPivot) {
                if ($metricName -eq "percentilesHist") {
                    $chartTitle = (Get-Culture).TextInfo.ToTitleCase("$prop Appx. Percentiles - $IPivotKey $innerPivot")
                } 
                else {
                    $chartTitle = (Get-Culture).TextInfo.ToTitleCase("$prop Percentiles - $IPivotKey $innerPivot")
                }
                    
                $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey
            } 
            else {
                if ($metricName -eq "percentilesHist") {
                    $chartTitle = (Get-Culture).TextInfo.ToTitleCase("$prop Appx. Percentiles")
                } 
                else {
                    $chartTitle = (Get-Culture).TextInfo.ToTitleCase("$prop Percentiles")
                }
                    
                $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey
            }
            
            $table = @{
                "rows" = @{
                    "percentiles" = @{}
                }
                "cols" = @{
                    $tableTitle = @{
                        $prop = 0
                    }
                }
                "meta" = @{
                    "columnFormats" = @($meta.format.$prop)
                    "rightAlign"    = [Array] @(2)
                }
                "data" = @{
                    $tableTitle = @{
                        $prop = @{
                            "percentiles" = @{}
                        }
                    }
                }
                "chartSettings" = @{
                    "title"     = $chartTitle
                    "yOffset"   = 1
                    "xOffset"   = 1
                    "chartType" = [Microsoft.Office.Interop.Excel.XlChartType]::xlXYScatterLinesNoMarkers
                    "seriesSettings" = @{
                        1 = @{ 
                            "color"      = $BLUES[1]
                            "lineWeight" = 3
                        }
                    }
                    "axisSettings" = @{
                        1 = @{
                            "max"            = 100
                            "title"          = "Percentiles"
                            "minorGridlines" = $true
                        }
                        2 = @{
                            "title" = $meta.units[$prop]
                        }
                    }
                }
            }

            if ($prop -eq "throughput") {
                $max = Get-ThroughputMax -Data $data -OPivotKey $OPivotKey -IPivotKey $IPivotKey -Meta $meta
                if ($max -ne -1) {
                    $table.chartSettings.axisSettings[2].max = $max
                }
            }
                
            $table.chartSettings.axisSettings[2].logarithmic = Set-Logarithmic -Data $data -OPivotKey $OPivotKey -Prop $prop -IPivotKey $IPivotKey -Meta $meta 

            if ($meta.comparison) {
                $table.cols.$tableTitle.$prop = @{
                    "baseline" = 0
                    "% change" = 1
                    "test"     = 2
                }
                $table.data.$tableTitle.$prop = @{
                    "baseline" = @{
                        "percentiles" = @{}
                    }
                    "% change" = @{
                        "percentiles" = @{}
                    }
                    "test" = @{
                        "percentiles" = @{}
                    }
                }
                $table.chartSettings.seriesSettings[2] = @{
                    "delete" = $true
                }
                $table.chartSettings.seriesSettings[3] = @{
                    "color"      = $ORANGES[1]
                    "lineWeight" = 3
                }
                $table.meta.columnFormats = @($meta.format.$prop, $meta.format."% change", $meta.format.$prop)
            }
            $row = 0

            $keys = @()
            if ($data.$OPivotKey.$prop.$IPivotKey.baseline.$metricName.Keys.Count -gt 0) {
                $keys = $data.$OPivotKey.$prop.$IPivotKey.baseline.$metricName.Keys
            } 
            else {
                $keys = $data.$OPivotKey.$prop.$IPivotKey.test.$metricName.Keys
            }

            # Add row labels and fill data in table
            foreach ($percentile in $keys | Sort) {
                $table.rows.percentiles.$percentile = $row
                if ($meta.comparison) {
                    $percentage = $data.$OPivotKey.$prop.$IPivotKey."% change".$metricName[$percentile]
                    $percentage = "$percentage %"

                    $table.data.$tableTitle.$prop.baseline.percentiles[$percentile]   = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.$metricName.$percentile}
                    $table.data.$tableTitle.$prop."% change".percentiles[$percentile] = @{"value" = $percentage}
                    $table.data.$tableTitle.$prop.test.percentiles[$percentile]       = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.test.$metricName.$percentile}
                    $params = @{
                        "Cell"    = $table.data.$tableTitle.$prop."% change".percentiles[$percentile]
                        "TestVal" = $data.$OPivotKey.$prop.$IPivotKey.test.$MetricName[$percentile]
                        "BaseVal" = $data.$OPivotKey.$prop.$IPivotKey.baseline.$MetricName[$percentile]
                        "Goal"    = $meta.goal.$prop
                    }
                    $table.data.$tableTitle.$prop."% change".percentiles[$percentile] = Select-Color @params
                } 
                else {
                    $table.data.$tableTitle.$prop.percentiles[$percentile] = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.$metricName.$percentile}
                }
                $row += 1
            
            }
            $table.meta.dataWidth     = Get-TreeWidth $table.cols
            $table.meta.colLabelDepth = Get-TreeDepth $table.cols
            $table.meta.dataHeight    = Get-TreeWidth $table.rows
            $table.meta.rowLabelDepth = Get-TreeDepth $table.rows
            $tables = $tables + $table
        }
    }

    if (($tables.Count -gt 0) -and (-not $NoNewWorksheets)) {
        $sheetTitle = Get-WorksheetTitle -BaseName "Percentiles" -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $tables     = @($sheetTitle) + $tables 
    }

    return $tables  
}

function Set-Logarithmic ($Data, $OPivotKey, $Prop, $IPivotKey, $Meta) {
    if ($data.$OPivotKey.$Prop.$IPivotKey.baseline.stats) {
        if ($data.$OPivotKey.$Prop.$IPivotKey.baseline.stats.min -le 0) {
            return $false
        }
        if ($Meta.comparison) {
            if ($data.$OPivotKey.$Prop.$IPivotKey.test.stats.min -le 0) {
                return $false
            }
        }
        if (($data.$OPivotKey.$Prop.$IPivotKey.baseline.stats.max / ($data.$OPivotKey.$Prop.$IPivotKey.baseline.stats.min + $EPS)) -gt 10) {
            return $true
        }

        if ($Meta.comparison) {
            if (($data.$OPivotKey.$Prop.$IPivotKey.test.stats.max / ($data.$OPivotKey.$Prop.$IPivotKey.test.stats.min + $EPS)) -gt 10) {
                return $true
            }
            if (($data.$OPivotKey.$Prop.$IPivotKey.test.stats.max / ($data.$OPivotKey.$Prop.$IPivotKey.baseline.stats.min + $EPS)) -gt 10) {
                return $true
            }
            if (($data.$OPivotKey.$Prop.$IPivotKey.baseline.stats.max / ($data.$OPivotKey.$Prop.$IPivotKey.test.stats.min + $EPS)) -gt 10) {
                return $true
            }
        }
    }
    return $false
}

function Get-ThroughputMax ($Data, $OPivotKey, $IPivotKey, $Meta) {
    $max = -1
    foreach ($val in $THROUGHPUTS) {
        if ($val -gt $Data.$OPivotKey.throughput.$IPivotKey.baseline.stats.max) {
            if ($Meta.comparison) {
                if ($val -gt $data.$OPivotKey.throughput.$IPivotKey.test.stats.max) {
                    $max = $val
                    break
                }
            } 
            else {
                $max = $val 
                break
            }
        }
    }
    return $max
}

function Format-Histogram {
    Param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter(Mandatory=$true)] $OPivotKey,

        [Parameter()] [String] $Tool = "",

        [Parameter()] [switch] $NoNewWorksheets
    )

    $legend = @{
        "meta" = @{
            "colLabelDepth" = 2
            "rowLabelDepth" = 2
            "dataWidth"     = 2
            "dataHeight"    = 3 
        }
        "rows" = @{
            " "   = 0
            " "  = 1
            " " = 2
        }
        "cols" = @{
            "legend" = @{
                " "  = 0
                " " = 1
            }
        }
        "data" = @{
            "legend" = @{
                " " = @{
                    " " = @{
                        "value" = "For side by side comparison,`n we compare each buckets share of its `ndataset's total latency samples.
"

                    }
                    " " = @{
                        "value" = "buckets whose share of total `nsamples increased are colored: "
                    }
                    " " = @{
                        "value" = "buckets whose share of total `nsamples decreased are colored:"
                    }
                }
                " " = @{
                    " " = @{
                        "value"     = "increase"
                        "fontColor" = $GREEN
                        "cellColor" = $LIGHTGREEN
                    }
                    " " = @{
                        "value"     = "decrease"
                        "fontColor" = $RED
                        "cellColor" = $LIGHTRED
                    }
                } 
            }
        }
    }

    $tables     = @()
    $data       = $DataObj.data
    $meta       = $DataObj.meta
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot 

    foreach ($prop in $data.$OPivotKey.Keys) {
        foreach ($IPivotKey in $data.$OPivotKey.$prop.Keys | Sort) {
            if (-Not $data.$OPivotKey.$prop.$IPivotKey.baseline.Histogram) {
                continue
            }

            $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey

            if ($IPivotKEy) {
                $chartTitle = (Get-Culture).TextInfo.ToTitleCase("$prop Histogram - $IPivotKey $innerPivot") 
            } else {
                $chartTitle = (Get-Culture).TextInfo.ToTitleCase("$prop Histogram") 
            }

            $units = $meta.units.$prop
            $table = @{
                "rows" = @{
                    "histogram buckets" = @{}
                }
                "cols" = @{
                    $tableTitle = @{
                        $prop = 0
                    }
                }
                "meta" = @{
                    "rightAlign" = [Array] @(2)
                }
                "data" = @{
                    $tableTitle = @{
                        $prop = @{
                            "histogram buckets" = @{}
                        }
                    }
                }
                "chartSettings"= @{
                    "title"   = $chartTitle
                    "yOffset" = 1
                    "xOffset" = 1
                    "seriesSettings" = @{
                        1 = @{ 
                            "color"      = $BLUES[1]
                            "lineWeight" = 3
                            "name" = "Sample Count"
                        }
                    }
                    "axisSettings" = @{
                        1 = @{
                            "title" = "$prop ($units)"
                            "tickLabelSpacing" = 5
                        }
                        2 = @{
                            "title" = "Count"
                        }
                    }
                }
            }
            
            if ($meta.comparison) {
                $table.cols.$tableTitle.$prop = @{
                    "baseline" = 0
                    "% change" = 1
                    "test"     = 2
                }
                $table.data.$tableTitle.$prop = @{
                    "baseline" = @{
                        "histogram buckets" = @{}
                    }
                    "% change" = @{
                        "histogram buckets" = @{}
                    }
                    "test" = @{
                        "histogram buckets" = @{}
                    }
                }
                $table.chartSettings.seriesSettings[2] = @{
                    "delete" = $true
                }
                $table.chartSettings.seriesSettings[1].name = "Baseline Sample Count"
                    
                $table.chartSettings.seriesSettings[3] = @{
                    "color"      = $ORANGES[1]
                    "name"       = "Test Sample Count"
                    "lineWeight" = 3
                }
                $table.meta.columnFormats = @($null, $meta.format."% change", $null)
            }
            $row = 0

            $keys = @()
            if ($data.$OPivotKey.$prop.$IPivotKey.baseline.histogram.Keys.Count -gt 0) {
                $keys = $data.$OPivotKey.$prop.$IPivotKey.baseline.histogram.Keys
            } else {
                $keys = $data.$OPivotKey.$prop.$IPivotKey.test.histogram.Keys
            }

            # Add row labels and fill data in table
            foreach ($bucket in ($keys | Sort)) {
                $table.rows."histogram buckets".$bucket = $row
                if ($meta.comparison) {
                    $percentage = $data.$OPivotKey.$prop.$IPivotKey."% change".histogram[$bucket]
                    $percentage = "$percentage %"

                    $table.data.$tableTitle.$prop.baseline."histogram buckets"[$bucket]   = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.histogram.$bucket}
                    $table.data.$tableTitle.$prop."% change"."histogram buckets"[$bucket] = @{"value" = $percentage}
                    $table.data.$tableTitle.$prop.test."histogram buckets"[$bucket]       = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.test.histogram.$bucket}
                    $params = @{
                        "Cell"    = $table.data.$tableTitle.$prop."% change"."histogram buckets"[$bucket]
                        "TestVal" = $data.$OPivotKey.$prop.$IPivotKey.test.histogram[$bucket]
                        "BaseVal" = $data.$OPivotKey.$prop.$IPivotKey.baseline.histogram[$bucket]
                        "Goal"    = "increase"
                    }
                    $table.data.$tableTitle.$prop."% change"."histogram buckets"[$bucket] = Select-Color @params
                } 
                else {
                    $table.data.$tableTitle.$prop."histogram buckets"[$bucket] = @{"value" = $data.$OPivotKey.$prop.$IPivotKey.baseline.histogram.$bucket}
                }
                $row += 1
            
            }
            $table.meta.dataWidth     = Get-TreeWidth $table.cols
            $table.meta.colLabelDepth = Get-TreeDepth $table.cols
            $table.meta.dataHeight    = Get-TreeWidth $table.rows
            $table.meta.rowLabelDepth = Get-TreeDepth $table.rows
            $tables = $tables + $table
        }
    }
    if ($table.Count -gt 0) {
        if ($meta.comparison) {
            $tables = @($legend) + $tables
        }
        $worksheetTitle = Get-WorksheetTitle -BaseName "Histogram" -OuterPivot $outerPivot -OPivotKey $OPivotKey
        $tables = @($worksheetTitle) + $tables
    }
    return $tables  
}


##
# Format-Distribution
# -------------------
# This function formats a table in order to create a chart that displays the the
# distribution of data over time.
#
# Parameters
# ----------
# DataObj (HashTable) - Object containing processed data, raw data, and meta data
# TableTitle (String) - Title to be displayed at the top of each table
# Prop (String) - The name of the property for which a table should be created (raw data must be in array form)
# SubSampleRate (int) - How many time samples should be grouped together for a single data point on the chart
#
# Return
# ------
# HashTable[] - Array of HashTable objects which each store a table of formatted distribution data
#
##
function Format-Distribution {
    Param (
        [Parameter(Mandatory=$true)] [PSobject[]] $DataObj,

        [Parameter()] [string] $OPivotKey,

        [Parameter()] [String] $Tool = "",

        [Parameter()] [String] $Prop = "latency",

        [Parameter()] [String] $NumSamples = 10000,
    
        [Parameter()] [Int] $SubSampleRate = 50,

        [Parameter()] [switch] $NoNewWorksheets
        
    )

    $meta  = $DataObj.meta 
    $modes = @("baseline")
    if ($meta.comparison) {
        $modes += "test"
    }
    $tables     = @()
    $innerPivot = $meta.InnerPivot
    $outerPivot = $meta.OuterPivot

    foreach ($IPivotKey in $DataObj.data.$OPivotKey.$Prop.Keys) {
        foreach ($mode in $modes) { 
            if (-Not $DataObj.data.$OPivotKey.$Prop.$IPivotKey.$mode.stats) {
                continue
            }
            $tableTitle = Get-TableTitle -Tool $Tool -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey
            $data       = $dataObj.rawData.$mode
            $table = @{
                "meta" = @{}
                "rows" = @{
                    "Data Point" = @{}
                }
                "cols" = @{
                    $tableTitle = @{
                        "Time Segment" = 0
                        $Prop          = 1
                    }
                }
                "data" = @{
                    $tableTitle = @{
                        "Time Segment" = @{
                            "Data Point" = @{}
                        }
                        "latency" = @{
                            "Data Point" = @{}
                        }
                    }
                }
                "chartSettings" = @{
                    "chartType" = [Microsoft.Office.Interop.Excel.XlChartType]::xlXYScatter
                    "yOffset"   = 2
                    "xOffset"   = 2
                    "title"     = "Temporal Latency Distribution"
                    "axisSettings" = @{
                        1 = @{
                            "title"          = "Time Series"
                            "max"            = $NumSamples
                            "minorGridlines" = $true
                            "majorGridlines" = $true
                        }
                        2 = @{
                            "title"       = "us"
                            "logarithmic" = $true
                            "min"         = 10
                        }
                    }
                }
            }

            if ($mode -eq "baseline") {
                $table.chartSettings.seriesSettings = @{
                    1 = @{
                            "markerStyle"           = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                            "markerBackgroundColor" = $BLUES[2]
                            "markerForegroundColor" = $BLUES[1]
                            "name"                  = "$prop Sample" 
                        }
                }
            } else {
                $table.chartSettings.seriesSettings = @{
                    1 = @{
                            "markerStyle"           = [Microsoft.Office.Interop.Excel.XlMarkerStyle]::xlMarkerStyleCircle
                            "markerBackgroundColor" = $ORANGES[2]
                            "markerForegroundColor" = $ORANGES[1]
                            "name"                  = "$prop Sample"
                        }
                }
            }

            # Add row labels and fill data in table
            $i   = 0
            $row = 0
            if ($SubSampleRate -gt 0) {
                $NumSegments = $NumSamples / $SubSampleRate
                while ($i -lt $NumSegments) {
                    [Array]$segmentData = @()
                    foreach ($entry in $data) {
                        if ($entry.$prop.GetType().Name -ne "Object[]") {
                            continue
                        }
                        if (((-not $innerPivot) -or ($entry.$innerPivot -eq $IPivotKey)) -and ((-not $outerPivot) -or ($entry.$outerPivot -eq $OPivotKey))) {
                            $segmentData += $entry[$Prop][($i * $SubSampleRate) .. ((($i + 1) * $SubSampleRate) - 1)]
                        }
                    }
                    $segmentData = $segmentData | Sort
                    $time        = $i * $subSampleRate
                    if ($segmentData.Count -ge 5) {
                        $table.rows."Data Point".$row       = $row
                        $table.rows."Data Point".($row + 1) = $row + 1
                        $table.rows."Data Point".($row + 2) = $row + 2
                        $table.rows."Data Point".($row + 3) = $row + 3
                        $table.rows."Data Point".($row + 4) = $row + 4
                        $table.data.$tableTitle."Time Segment"."Data Point".$row       = @{"value" = $time}
                        $table.data.$tableTitle."Time Segment"."Data Point".($row + 1) = @{"value" = $time}
                        $table.data.$tableTitle."Time Segment"."Data Point".($row + 2) = @{"value" = $time}
                        $table.data.$tableTitle."Time Segment"."Data Point".($row + 3) = @{"value" = $time}
                        $table.data.$tableTitle."Time Segment"."Data Point".($row + 4) = @{"value" = $time}
                        $table.data.$tableTitle.$Prop."Data Point".$row = @{"value"       = $segmentData[0]}
                        $table.data.$tableTitle.$Prop."Data Point".($row + 1) = @{"value" = $segmentData[[int]($segmentData.Count / 4)]}
                        $table.data.$tableTitle.$Prop."Data Point".($row + 2) = @{"value" = $segmentData[[int]($segmentData.Count / 2)]}
                        $table.data.$tableTitle.$Prop."Data Point".($row + 3) = @{"value" = $segmentData[[int]((3 * $segmentData.Count) / 4)]}
                        $table.data.$tableTitle.$Prop."Data Point".($row + 4) = @{"value" = $segmentData[-1]}
                        $row += 5
                    } 
                    else {
                        foreach ($sample in $segmentData) {
                            $table.rows."Data Point".$row = $row
                            $table.data.$tableTitle."Time Segment"."Data Point".$row = @{"value" = $time}
                            $table.data.$tableTitle.$Prop."Data Point".$row          = @{"value" = $sample}
                            $row++
                        }
                    }
                    $i++
                }
            } else {
                while ($i -lt $NumSamples) {
                    [Array]$segmentData = @()
                    foreach ($entry in $data) {
                        if ($entry.$prop.GetType().Name -ne "Object[]") {
                            continue
                        }
                        if (((-not $innerPivot) -or ($entry.$innerPivot -eq $IPivotKey)) -and ((-not $outerPivot) -or ($entry.$outerPivot -eq $OPivotKey))) {
                            $segmentData += $entry[$Prop][$i]
                        }
                    }

                    foreach ($sample in $segmentData) {
                        $table.rows."Data Point".$row = $row
                        $table.data.$tableTitle."Time Segment"."Data Point".$row = @{"value" = $i}
                        $table.data.$tableTitle.$Prop."Data Point".$row          = @{"value" = $sample}
                        $row++
                    }
                    $i++
                }
            }
            $table.meta.dataWidth     = Get-TreeWidth $table.cols
            $table.meta.colLabelDepth = Get-TreeDepth $table.cols
            $table.meta.dataHeight    = Get-TreeWidth $table.rows
            $table.meta.rowLabelDepth = Get-TreeDepth $table.rows

            if (-not $NoNewWorksheets) {
                if ($modes.Count -gt 1) {
                    if ($mode -eq "baseline") {
                        $worksheetName = Get-WorksheetTitle -BaseName "Base Distr." -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey
                    } 
                    else {
                        $worksheetName = Get-WorksheetTitle -BaseName "Test Distr." -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey
                    } 
                } 
                else {
                    $worksheetName = Get-WorksheetTitle -BaseName "Distr." -OuterPivot $outerPivot -OPivotKey $OPivotKey -InnerPivot $innerPivot -IPivotKey $IPivotKey
                }
                
                $tables += $worksheetName
            }

            $tables += $table
        }
    }
    return $tables
}


##
# Select-Color
# ------------
# This function selects the color of a cell, indicating whether a test value
# shows an improvement when compared to a baseline value. Improvement is defined
# by the goal (increase/decrease) for the given value.
#
# Parameters
# ----------
# Cell (HashTable) - Object containg a cell's value and other settings
# TestVal (decimal) - Test value
# BaseVal (decimal) - Baseline value
# Goal (String) - Defines improvement ("increase" or "decrease")
#
# Return
# ------
# HashTable - Object containing a cell's value and other settings
#
##
function Select-Color ($Cell, $TestVal, $BaseVal, $Goal) {
    if ( $Goal -eq "increase") {
        if ($TestVal -ge $BaseVal) {
            $Cell["fontColor"] = $GREEN
            $Cell["cellColor"] = $LIGHTGREEN
        } 
        else {
            $Cell["fontColor"] = $RED
            $Cell["cellColor"] = $LIGHTRED
        }
    } 
    else {
        if ($TestVal -le $BaseVal) {
            $Cell["fontColor"] = $GREEN
            $Cell["cellColor"] = $LIGHTGREEN
        } 
        else {
            $Cell["fontColor"] = $RED
            $Cell["cellColor"] = $LIGHTRED
        }
    }
    return $cell
}


##
# Get-TreeWidth
# -------------
# Calculates the width of a tree structure
#
# Parameters
# ----------
# Tree (HashTable) - Object with a heirarchical tree structure
#
# Return
# ------
# int - Width of Tree
#
##
function Get-TreeWidth ($Tree) {
    if ($Tree.GetType().Name -eq "Int32") {
        return 1
    }
    $width = 0
    foreach ($key in $Tree.Keys) {
        $width += [int](Get-TreeWidth -Tree $Tree[$key])
    }
    return $width
}

##
# Get-TreeWidth
# -------------
# Calculates the depth of a tree structure
#
# Parameters
# ----------
# Tree (HashTable) - Object with a heirarchical tree structure
#
# Return
# ------
# int - Depth of Tree
#
##
function Get-TreeDepth ($Tree) {
    if ($Tree.GetType().Name -eq "Int32") {
        return 0
    }
    $depths = @()
    foreach ($key in $Tree.Keys) {
        $depths = $depths + [int](Get-TreeDepth -Tree $Tree[$key])
    }
    return ($depths | Measure -Maximum).Maximum + 1
}

##
# Sort-ByProp
# -------------
# Sorts an array of objects by the value of a specified property in each object
#
# Parameters
# ----------
# Data (HashTable[]) - Array of objects
# Prop (String) - Name of property to sort by
#
# Return
# ------
# HashTable[] - Array of objects, sorted by property value
#
##
function Sort-ByProp {
    param(
        [Parameter()]
        [PSObject] $Data,

        [Parameter()]
        [string] $Prop
    )

    if ($Data.length -eq 1) {
        $sorted = @()
        $sorted = $sorted + $Data
        return $sorted
    }
    $arr1 = $Data[0 .. ([int]($Data.length / 2) - 1)]
    $arr2 = $Data[[int]($Data.length / 2) .. ($Data.length - 1)]

    [array] $arr1 = Sort-ByProp -Data $arr1 -Prop $prop
    [array] $arr2 = Sort-ByProp -Data $arr2 -Prop $prop
    $sorted = @()
    $idx1 = 0
    $idx2 = 0
    
    while ($idx1 -lt $arr1.length -and $idx2 -lt $arr2.length) {
        if ($arr1[$idx1].$prop -le $arr2[$idx2].$prop) {
            $sorted  = $sorted + $arr1[$idx1]
            $idx1   += 1
        } 
        else {
            $sorted  = $sorted + $arr2[$idx2]
            $idx2   += 1
        }
    }

    while ($idx1 -lt $arr1.length) {
        $sorted  = $sorted + $arr1[$idx1]
        $idx1   += 1
    }

    while ($idx2 -lt $arr2.length) {
        $sorted  = $sorted + $arr2[$idx2]
        $idx2   += 1
    }
    return $sorted
}