Tables - Table of Differences

From Q
Jump to: navigation, search

Compare two tables and by performing t-Tests on each cell with the corresponding cell in the other table.

Compare two tables by comparing each cell in one table with the corresponding cell in the other table. If the primary statistic in the tables is a percentage computed from categorical variables, then the type of test used to compare the values will be determined the test for Proportions specified in Statistical Assumptions. If the primary statistic is an average computed from numeric variables, then the test for Means will be used instead. A number of different options are available for displaying significant values.

Example

Options

Table 1 and Table 2 are the input tables to be compared. They are expected to have the same row and column names and the same primary statistic. The primary statistic can be one of "Average", "%", "Total %", "Column %" or "Row %". They must also have an appropriate cell statistics for Sample Size and Standard Error for the primary statistic used. For example when the primary statistic is "Column %", the input tables must also contain "Column n" and "Column Standard Error".

Show This option controls which values are shown in the output table. It does not affect the test statistic or p-values. It can be one of

Differences between Table 2 - Table 1.
Primary statistic of Table 2 with differences not shown but reflected by the shading options.
Primary statistic of Table 2 with differences with separate formatting controls for the primary statistic and the difference values.

Show significant values by shading in controls which element is shaded to reflect the p-values from the t-test performed on the two tables.

None No shading is applied
Cell colors Shading is applied to the cell fill color
Arrows Shading is applied to up/down arrows next to the cell value
Boxes Shading is applied to a box drawn around the cell text. Additional controls are associated with this option. A separate control for the border color is provided for each significance level and the border width, corner roundness, and padding around the box can be adjusted. Note that the padding is specified in terms of pixels, so if the size of the output does not provide have enough space to accommodate the box including its padding, the box will be truncated.

Number of significance levels controls the number of thresholds (and corresponding shades) are applied to the table.

Threshold Controls which cells are shaded. The threshold is compared against the p-values from the independent t-Tests.

Rows/Columns to ignore A comma-separated list of row/column names which should not be shown in the output table.

Font family The font family for all text in the table

Font size The font size of all text in the table.

Font units The units in which the font size is specified. This can be either "px" or "pt".

Number of decimals shown Separate controls are shown for the primary statistic and difference.

Prefix/Suffix Optional text to prepend or append to the primary statistic/difference.

Font family/color/size of primary statistic Font controls for the primary statistic and difference (separately). These will default font controls, but where they differ these controls to allow different fonts to be shown in different setting.

Automatically determine the font color Text will be automatically colored black or white depending on the background color. It takes into account the cell fill color, and conditional shading (both in cell color and box).

Show +/- sign on differences Always prepend differences with ‘+’ or ‘-‘.

Show legend Whether of nor to show a legend explaining the shaded cells/boxes/arrows in the footer of the table.

Show column/row headers Whether to show column/row headers for the table. In some cases, whether the Table of Differences is shown next to other visual elements, information about the row/column names may already be present.

Show borders around row/collumn headers By default, borders are only placed around the table cells, but they can be extended to include the header/row headers.

Row height automatically fills R output This is the default option. Users can adjust the spacing by dragging and resizing the output. Note, however, that if the table has many rows, then it may be better to set it to a fixed row height.

Row height The height of a row in the table (with no word wrap). This option is only shown if row height does not automatically fill R output. This value will be specified in terms of font units. By default it is 5 + font size. If there are too many rows to show, a scrollbar will be shown.

Column widths A column separated list of values (including units, e.g. "px", "pt", "%") specifying the widths of the column. Each value will be a single column starting from the left. The remaining columns with no specified widths will be equally sized to fill the remaining space. Note that the first value will be applied to the row headers (if they are shown).

Column header fill The color of the column header cells.

Row header fill The color of the row header cells containing the names of the profiling variables.

Cell fill The color of the cells (excluding row/column headers) and cells colored by conditional formatting.

Collapse borders Whether the borders of adjacent cells should be collapsed into a single line. This is default, but there is also the option to not collapse borders, in which case the ‘’’gap between rows’’’ or ‘’’gap between columns’’’ can be manually adjusted.

Border color The color of the border.

Border width The width of the border in pixels.

Code

var default_output = "Table of Differences";

// VERSION 1.0.2
var allow_control_groups = Q.fileFormatVersion() > 10.9; // Group controls for Displayr and later versions of Q
var controls = []; // All controls displayed must be inserted here
// * Combo box alternatives *
var template_prompt = "Create a template to control color palettes and fonts for all visualizations in the document using " + (displayr ? "'Insert > Utilities > Visualization > Create Template'" : "'Automate > Browse Online Library > Visualization > Create Template'")
var format_prompt = "Format automatically determined based on input data. Click on up/down buttons to adjust";
categories_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
values_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
hover_number_formats = ["Automatic", "Number", "Category", "Percentage", "Metric units suffix", "Scientific", "Custom"]; // no currency since cannot add as prefix and no date because of charting requirements
data_label_formats = ["Automatic", "Number", "Percentage"];
date_formats = ["YY (Year, 2 digit)", "DD Mon YY", "DD Month YY", "DD MM YY", "YYYY (Year, 4 digit)", "DD Mon YYYY", "DD Month YYYY", "DD MM YYYY", "Mon DD YY", "Month DD YY", "MM DD YY", "Mon DD YYYY", "Month DD YYYY", "MM DD YYYY", "YY Mon DD", "YY Month DD", "YY MM DD", "YYYY Mon DD", "YYYY Month DD", "YYYY MM DD", "Custom"];
var marker_symbols = ["circle", "circle-open", "square", "square-open", "diamond", "diamond-open"];
var displayr = Q.isOnTheWeb();
function isEmpty(x) { return (x == undefined || x.getValue() == null && (x.getValues() == null || x.getValues().length == 0)) }
function isBlankSheet(x) { return (x.getValue() == null || x.getValue().length == 0) }
function isEmptyString(x) { return (x == undefined || x == null || x == "") }

 
// * Font alternatives *
font_families = ["Arial", "Arial Black", "Comic Sans MS",  "Courier New", "Georgia", "Impact", 
                 "Open Sans", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana"];
var use_default_fonts = false;
var globalFontFamily = "Arial";
var globalFontColor = "#2C2C2C";
var globalFontSize = 7.5;

// * Palette alternatives *
palettes = ["Default or template settings", "Default colors", "Colorblind safe colors", "Rainbow", "Light pastels", "Strong colors", "Spectral colors (red, yellow, blue)", "Spectral colors (blue, yellow, red)", "Reds, dark to light", "Reds, light to dark", "Greens, dark to light", "Greens, light to dark", "Blues, dark to light", "Blues, light to dark", "Greys, dark to light", "Greys, light to dark", "Heat colors (yellow, red)", "Terrain colors (green, beige, grey)", "Custom color", "Custom gradient", "Custom palette"];

/* Some functions to create a group of controls */
numberFormatControls = function(name, prefix, controls, number_formats, addPrefix = true, showCustomizations = true)
{
    var qNumberType = form.comboBox({name: "form" + name + "NumberType", label: prefix + " number type", alternatives: number_formats, default_value: number_formats[0], required: true});
    controls.push(qNumberType);
    var yNumberType = qNumberType.getValue();
    if (yNumberType == "Date/Time") // funny loop because date/time can be custom
    {
        var qDate = form.comboBox({name: "form" + name + "DateType", label: "Date type", alternatives: date_formats, default_value: date_formats[0], required: true});
        yNumberType = qDate.getValue();
        controls.push(qDate);
    }
    
    // Custom format
    if (showCustomizations)
    {
        var qNumberCustom = null;
        if (yNumberType == "Custom")
            qNumberCustom = form.textBox({name: "form" + name + "NumberCustom", label: "Custom code", default_value: "%d %b %y", required: true, prompt: "D3 formatting code, e.g. ,.2%"});
        else if (yNumberType == 'Currency')
            qNumberCustom = form.textBox({name: "form" + name + "Currency", label: "Currency symbol", default_value: "$", required: true}); 
        else if (yNumberType == 'Number' || yNumberType == 'Currency') 
            qNumberCustom = form.checkBox({name: "form" + name + "SeparateThousands", label: "Separate thousands by comma", default_value: true});
        if (qNumberCustom != null)
            controls.push(qNumberCustom);
        
        controls.push(form.numericUpDown({name: "form" + name + "Decimals", label: yNumberType == "Metric unit suffix" ? "Significant digits": "Decimal places", minimum: yNumberType == "Metric unit suffix" ? 1 : 0, required: false, prompt: format_prompt}));
        if (addPrefix)
        {
            controls.push(form.textBox({name: "form" + name + "Prefix", label: "Custom prefix", default_value: "", required: false, prompt: "Optional text to prepend to the labels"})); 
            controls.push(form.textBox({name: "form" + name + "Suffix", label: "Custom suffix", default_value: "", required: false, prompt: "Optional text to append to the labels"})); 
        }
    }
    return(yNumberType);
}

fontControls = function(name, prefix, controls, fontSize = 7.5, adjustColor = true, colorOptions = false)
{
    var qFontDefault = form.checkBox({name: "form" + name + "FontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
    controls.push(qFontDefault);
    if (!qFontDefault.getValue())
    {
        controls.push(form.comboBox({name: "form" + name + "FontFamily", label: prefix + " font family", alternatives: font_families, default_value: globalFontFamily}));
        if (adjustColor)
        {
            if (colorOptions)
            {
                var altcols = ['Automatically', 'In a single color', 'In different colors for each series'];
                var colOpts = form.comboBox({name: "form" + name + "FontColorOptions", label: "Color " + prefix, alternatives: altcols, default_value: altcols[0]});
                controls.push(colOpts);
                if (colOpts.getValue() == "In a single color")
                    controls.push(form.colorPicker({name: "form" + name + "FontColor", label: prefix + " font color", default_value: globalFontColor}));
                else if (colOpts.getValue() == "In different colors for each series")
                    controls.push(form.textBox({name: "form" + name + "FontColor", label: prefix + " font color(s)", default_value: rgbToHex(globalFontColor), prompt: "Enter a hex code or comma-separated list of hex code for each series in the data"}));
            }
            else
                controls.push(form.colorPicker({name: "form" + name + "FontColor", label: prefix + " font color", default_value: globalFontColor}));

        }
        controls.push(form.numericUpDown({name: "form" + name + "FontSize", label: prefix + " font size", default_value: fontSize, increment: 0.5}));
    }
}
formattedTextControls = function(name, prefix, controls, fontSize, promptText, 
                                 alwaysFormat = false, wrap = false, wrapDefault = 20, textbox = true)
{
    if (textbox)
    {
        var qText = form.textBox({name: "form" + name, label: prefix, required: false, prompt: promptText});
        var textOpt = qText.getValue();
        controls.push(qText);
    }
    if (alwaysFormat || !textbox || !isEmptyString(textOpt))
    {
        var qTextFontDefault = form.checkBox({name: "form" + name + "FontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qTextFontDefault);
        if (!qTextFontDefault.getValue())
        {
            controls.push(form.comboBox({name: "form" + name + "FontFamily", label: prefix + " font family", alternatives: font_families, default_value: globalFontFamily}));
            controls.push(form.colorPicker({name: "form" + name + "FontColor", label: prefix + " font color", default_value: globalFontColor}));
            controls.push(form.numericUpDown({name:"form" + name + "FontSize", label: prefix + " font size", default_value: fontSize, increment: 0.5}));
        }
        if (wrap)
        {
            var qTextWrap = form.checkBox({name: "form" + name + "Wrap", label: "Wrap " + prefix, default_value: true});
            controls.push(qTextWrap);
            if (qTextWrap.getValue())
            {
                var qTextWrapN = form.numericUpDown({name: "form" + name + "WrapNchar", label: prefix + " width (in characters)", default_value: wrapDefault, minimum: 5, increment: 5, maximum: 5000});
                controls.push(qTextWrapN);
            }
        }
    }
}

colorPaletteControls = function(suffix, labelSuffix, palettes, controls, addColorValues = false)
{
        var qColor = form.comboBox({name: "form" + suffix + "Palette", label: "Color palette" + labelSuffix, alternatives: palettes, default_value: palettes[0], required: true});
        controls.push(qColor);
        var colOpt = qColor.getValue();
        if (colOpt == "Custom color")
        {
            controls.push(form.colorPicker({name: "form" + suffix + "CustomColor", label: "Custom color" + labelSuffix, default_value: default_colors[0]}));
        }
        if (colOpt == "Custom gradient")
        {
            controls.push(form.colorPicker({name: "form" + suffix + "CustomGradientStart", label: "Gradient start" + labelSuffix, default_value: "#0000FF"}));
            controls.push(form.colorPicker({name: "form" + suffix + "CustomGradientEnd", label: "Gradient end" + labelSuffix, default_value: "#FF0000"}));
        }
        if (colOpt == "Custom palette")
        {
            controls.push(form.textBox({name: "form" + suffix + "CustomPalette", label: "Custom palette" + labelSuffix, default_value: "#0000FF, #FF0000", prompt: "Enter color as a string. Multiple values should be separated by commas."}));
        }
        if (colOpt == "Custom palette (color pickers)")
        {
            for (i = 1; i <= 12; i++)
            {
                controls.push(form.colorPicker({name: "form" + suffix + "CustomPalette" + i, label: "Color " + i, 
                        default_value: default_colors[i-1]})); 
            }
        }
        if (colOpt == "Custom palette (R output)")
            controls.push(form.dropBox({name: "form" + suffix + "CustomPaletteROutput", label: "Custom palette", required: true,
                            types: ["RItem:character", "Table"], prompt: "Character R output containing a list of colors"}));

}

// We assume guid is unique so it can
// return as soon as a match has been found
function recursiveGetItemByGuid(group_item, guid)
{
    var cur_sub_items = group_item.subItems;
    for (var j = 0; j < cur_sub_items.length; j++)
    {
        if (cur_sub_items[j].type == 'ReportGroup') {
            var res = recursiveGetItemByGuid(cur_sub_items[j], guid);
            if (res != null)
                return(res)
        }
        else if (cur_sub_items[j].guid == guid)
            return(cur_sub_items[j]);
    }
    return null;
}

/* Create GUI form */
var outputCtl = form.comboBox({name: "formOutput", label: "Output", alternatives: ["Table of Differences", "Stacked Column Chart with Statistical Significance"], default_value: default_output});
controls.push(outputCtl);
var output = outputCtl.getValue();
form.setHeading(output);

var show_columncomparisons = false;
var tb1Ctl = form.dropBox({label: "Table 1", name: "formTable1", types: ["Table", "RItem:array"]});
controls.push(tb1Ctl);
// Looking for tb1 needs to occur immediately after this control
if (output != "Table of Differences" && tb1Ctl.getValue() != null)
{
    var tb1 = recursiveGetItemByGuid(project.report, tb1Ctl.getValue().guid);
    if (tb1 != null && tb1.cellStatistics.indexOf("Column Comparisons") != -1)
        show_columncomparisons = true;
}
var tb2Ctl = form.dropBox({label: "Table 2", name: "formTable2", types: ["Table", "RItem:array"], required: output == "Table of Differences"});
controls.push(tb2Ctl);
if (tb2Ctl.getValue() != null)
    show_columncomparisons = false;


if (output == "Stacked Column Chart with Statistical Significance")
{

    var numCatBelowCtl = form.numericUpDown({name: "formNumCatBelowAxis", label: "Number of categories below axis", default_value: 0});
    controls.push(numCatBelowCtl);
    var numCatBelow = numCatBelowCtl.getValue();
    var totalsAboveCtl = form.checkBox({name: "formShowTotalsAbove", label: "Column totals - above", default_value: true});
    controls.push(totalsAboveCtl);
    if (numCatBelow > 0)
    {
        var totalsBelowCtl = form.checkBox({name: "formShowTotalsBelow", label: "Column totals - below", default_value: true});
        controls.push(totalsBelowCtl);
    }
}

if (output == "Table of Differences")
{
    var showAlt = ["Differences", "Primary statistic of Table 2", "Primary statistic of Table 2 with differences"];
    var valuesShownCtl = form.comboBox({label: "Show", name: "formShowVal", alternatives: showAlt, default_value: showAlt[0], prompt: "Select which values to show in the cells of the output table."});
controls.push(valuesShownCtl);
var valuesShown = valuesShownCtl.getValue();

    form.group({label:"Significant Values", expanded: true})
    var condShadeCtl = form.comboBox({name: "formCondShadeType", label: "Show significant differences by shading in", alternatives: ["None", "Cell colors", "Boxes", "Arrows"], default_value: "Cell colors", prompt: "Select whether cells or elements inside the cell should be shaded to highlight the magnitude of the cell value"});
    controls.push(condShadeCtl);
    var condShade = condShadeCtl.getValue();
    if (condShade != "None")
    {
        var numLevelsCtl = form.numericUpDown({label: "Number of significance levels", name: "formNumLevels", default_value: 2});
        controls.push(numLevelsCtl);
        var numLevels = numLevelsCtl.getValue();
    }
    function componentToHex(c) { var hex = Math.floor(c).toString(16); return hex.length == 1 ? "0" + hex : hex; }
    function rgbToHex(x) { return "#" + componentToHex(x[0]) + componentToHex(x[1]) + componentToHex(x[2]); }


    if (condShade != "None")
    {
        for (var i = 1; i <= numLevels; i++)
        {
            var alphaCtl = form.numericUpDown({name: "formCondCutoff" + i, label: "Significance level "+i, default_value: i * 0.025, increment: 0.001, prompt: "A t-Test will be computed to compare the values in Table 2 to the corresponding values in Table 1. Cells will be shaded if the p-value from the t-Test is less than significance level " + i + "."});
            controls.push(alphaCtl);
            var alpha = alphaCtl.getValue();

            var sc = 255/(numLevels + 1);
            var cU = [i * sc, i * sc, 255];
            var cL = [255, i * sc, i * sc];
            var colUCtl = form.colorPicker({name: "formCondShadeUB" + i, label: "Fill color for increased values in Table 2", prompt: "Cells will be shaded in this color when values in Table 2 is significantly greater than Table 1 at the " + (1-alpha)*100 + "% confidence level", default_value: rgbToHex(cU)});
            controls.push(colUCtl);
            var colU = colUCtl.getValue();
            if (condShade == "Boxes")
                controls.push(form.colorPicker({name: "formCondShadeUBBorder" + i, label: "Border color for increase values in Table 2", prompt: "Box borders will be shaded in this color when values in Table 2 is significantly greater than Table 1 at the " +  (1-alpha)*100 + "% confidence level", default_value: colU}));

            var colLCtl = form.colorPicker({name: "formCondShadeLB" + i, label: "Fill color for decreased values in Table 2", prompt: "Cells will be shaded in this color when values in Table 2 is significantly less than Table 1 at the " + (1-alpha)*100 + "% confidence level", default_value: rgbToHex(cL)});
            controls.push(colLCtl);
            var colL = colLCtl.getValue();
            if (condShade == "Boxes")
                controls.push(form.colorPicker({name: "formCondShadeLBBorder" + i, label: "Border color for decreased values in Table 2", prompt: "Box borders will be shaded in this color when values in Table 2 is significantly less than Table 1 at the " +  (1-alpha)*100 + "% confidence level", default_value: colL}));

        }
    }

    if (condShade == "Boxes")
    {
        controls.push(form.numericUpDown({name: "formCondBoxWidth", label: "Box border width", default_value: 2}));
        controls.push(form.numericUpDown({name: "formCondBoxRadius", label: "Box corner roundness", default_value: 0, maximum: 50, prompt: "Increase value to get rounder corner; setting to 50 gives ovals"}));
        controls.push(form.numericUpDown({name: "formCondBoxPaddingTop", label: "Box top padding", default_value: 5, prompt: "Space between edge of box and text in pixels"}));
        controls.push(form.numericUpDown({name: "formCondBoxPaddingBottom", label: "Box bottom padding", default_value: 5, prompt: "Space between edge of box and text in pixels"}));
        controls.push(form.numericUpDown({name: "formCondBoxPaddingLeft", label: "Box left padding", default_value: 5, prompt: "Space between edge of box and text in pixels"}));
        controls.push(form.numericUpDown({name: "formCondBoxPaddingRight", label: "Box right padding", default_value: 5, prompt: "Space between edge of box and text in pixels"}));
    }
}

if (output == "Stacked Column Chart with Statistical Significance")
{
    controls.push(form.checkBox({name: "formTranspose", label: "Switch rows and columns", default_value: false}));
    controls.push(form.checkBox({name: "formColumnsReverse", label: "Reverse columns", default_value: false}));
}
controls.push(form.textBox({label: "Rows to ignore", type: "text", default_value: "NET, Total, SUM", name: "formIgnoreRows", required: false, prompt: "Specify rows to hide as a comma seperated list of row names"}));
controls.push(form.textBox({label: "Columns to ignore", type: "text", default_value: "NET, Total, SUM", name: "formIgnoreColumns", required: false, prompt: "Specify columns to hide as a comma seperated list of column names"}));

if (output == "Table of Differences")
{
    form.page("Format");
    form.group("Font");
    var fontFamilyCtl = form.comboBox({name: "formFontFamily", label: "Font family", default_value: "Arial", alternatives: font_families});
    controls.push(fontFamilyCtl);
    var fontFamily = fontFamilyCtl.getValue();
    var fontSizeCtl = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 10});
    controls.push(fontSizeCtl);
    var fontSize = fontSizeCtl.getValue();
    controls.push(form.comboBox({name: "formFontUnits", label: "Font units", alternatives: ["pt", "px"], default_value: "pt"}));
    var fontColorCtl = form.colorPicker({name: "formFontColor", label: "Font color", default_value: "#2C2C2C"});
    controls.push(fontColorCtl);
    var fontColor = fontColorCtl.getValue();

    form.group("Cell text");
    if (valuesShown == showAlt[1] || valuesShown == showAlt[2]) 
    {
        controls.push(form.numericUpDown({label: "Number of decimals shown in primary statistic", name: "formPStatDecimals", default_value: 0}));
        controls.push(form.textBox({label: "Prefix", name: "formPStatPrefix", required: false, prompt: "Optional text to prepend to the primary statistic"}));
        controls.push(form.textBox({label: "Suffix", name: "formPStatSuffix", required: false, prompt: "Optional text to append to the primary statistic"}));
        controls.push(form.comboBox({name: "formPStatFontFamily", label: "Font family of primary statistic", alternatives: font_families, default_value: fontFamily}));
        controls.push(form.numericUpDown({name: "formPStatFontSize", label: "Font size of primary statistic", default_value: fontSize}));
        var pstatAutoFontColorCtl = form.checkBox({label: "Automatically determine font color", name: "formPStatFontAutocolor", default_value: true, prompt: "Use black or white text depending on the cell fill color or box fill color"});
        controls.push(pstatAutoFontColorCtl);
        var pstatAutoFontColor = pstatAutoFontColorCtl.getValue();
        if (!pstatAutoFontColor)
            controls.push(form.colorPicker({name: "formPStatFontColor", label: "Font color of primary statistic", default_value: fontColor}));
    }

    if (valuesShown == showAlt[0] || valuesShown == showAlt[2]) 
    {
        var prefix_prompt = "";
        var prefix_default = "";
        if (valuesShown == showAlt[2])
        {
            prefix_prompt = "(default is a single space)";
            prefix_default = " ";
        }

        controls.push(form.numericUpDown({label: "Number of decimals shown in differences", name: "formDiffDecimals", default_value: 0}));
        controls.push(form.textBox({label: "Prefix", name: "formDiffPrefix", required: false, default_value: prefix_default, prompt: "Optional text to prepend to the differences" + prefix_prompt}));
        controls.push(form.textBox({label: "Suffix", name: "formDiffSuffix", required: false, prompt: "Optional text to append to the differences"}));
        controls.push(form.checkBox({label: "Show +/- sign on differences", name: "formDiffSign", default_value: true}));
        controls.push(form.comboBox({name: "formDiffFontFamily", label: "Font family of differences", alternatives: font_families, default_value: fontFamily}));
        controls.push(form.numericUpDown({name: "formDiffFontSize", label: "Font size of differences", default_value: fontSize}));
        var diffAutoFontColorCtl = form.checkBox({label: "Automatically determine font color", name: "formDiffFontAutocolor", default_value: true, prompt: "Use black or white text depending on the cell fill color or box fill color"});
        var diffAutoFontColor = diffAutoFontColorCtl.getValue();
        controls.push(diffAutoFontColorCtl);
        if (!diffAutoFontColor)
            controls.push(form.colorPicker({name: "formDiffFontColor", label: "Font color of differences", default_value: fontColor}));
    }

    form.group("Legend");
    var showLegend = form.checkBox({name: "formLegendShow", label: "Show legend", default_value: true});
    controls.push(showLegend);
    if (showLegend.getValue())
    {
        controls.push(form.comboBox({name: "formLegendFontFamily", label: "Font family", alternatives: font_families, default_value: fontFamily}));
        controls.push(form.numericUpDown({name: "formLegendFontSize", label: "Font size", default_value: 8}));
        controls.push(form.colorPicker({name: "formLegendFontColor", label: "Font color", default_value: fontColor}));
    }

    form.group("Column Headers");
    var showColumnHeadersCtl = form.checkBox({name: "formColumnHeadersShow", label: "Show column headers", default_value: true});
    controls.push(showColumnHeadersCtl);
    var showColumnHeaders = showColumnHeadersCtl.getValue();
    if (showColumnHeaders)
    {
        controls.push(form.checkBox({name: "formColumnHeadersBorder", label: "Show border around column headers", default_value: false}));
        controls.push(form.comboBox({name: "formColumnHeadersFontWeight", label: "Font weight", alternatives: ["Normal", "Bold"], default_value: "Normal"}));
        controls.push(form.comboBox({name: "formColumnHeadersFontFamily", label: "Font family", alternatives: font_families, default_value: fontFamily}));
        controls.push(form.numericUpDown({name: "formColumnHeadersFontSize", label: "Font size", default_value: fontSize}));
        controls.push(form.colorPicker({name: "formColumnHeadersFontColor", label: "Font color", default_value: fontColor}));
    }

    form.group("Row Headers");
    var showRowHeadersCtl = form.checkBox({name: "formRowHeadersShow", label: "Show row headers", default_value: true});
    var showRowHeaders = showRowHeadersCtl.getValue();
    controls.push(showRowHeadersCtl);
    if (showRowHeaders)
    {
        controls.push(form.checkBox({name: "formRowHeadersBorder", label: "Show border around row headers", default_value: false}));
        var rheadAlignCtl = form.comboBox({name: "formRowHeadersAlignHoriz", label: "Alignment", alternatives: ["Left", "Center", "Right"], default_value: "Right"});
        var rheadAlign = rheadAlignCtl.getValue();
        controls.push(rheadAlignCtl);
        if (rheadAlign != "Center")
            controls.push(form.numericUpDown({name: "formRowHeadersPad", label: "Padding", default_value: 5, prompt: "Space (in pixels) between the row header and the edge to the cell"}));
        controls.push(form.comboBox({name: "formRowHeadersFontWeight", label: "Font weight", alternatives: ["Normal", "Bold"], default_value: "Normal"}));
        controls.push(form.comboBox({name: "formRowHeadersFontFamily", label: "Font family", alternatives: font_families, default_value: fontFamily}));
        controls.push(form.numericUpDown({name: "formRowHeadersFontSize", label: "Font size", default_value: fontSize}));
        controls.push(form.colorPicker({name: "formRowHeadersFontColor", label: "Font color", default_value: fontColor}));
    }


    form.group("Spacing");
    var autoRowHeightCtl = form.checkBox({name: "formRowHeightAuto", label: "Row height automatically fills R output", default_value: true});
    var autoRowHeight = autoRowHeightCtl.getValue();
    controls.push(autoRowHeightCtl);
    if (!autoRowHeight)
        controls.push(form.numericUpDown({name: "formRowHeight", label: "Row height", default_value: fontSize + 5}));
    controls.push(form.textBox({name: "formColumnWidths", label: "Column widths", required: false, prompt: "Comma separated values, e.g. '40px, 25%' or leave blank for equal widths"}));

    form.group("Fill color");
    if (showColumnHeaders)
        controls.push(form.colorPicker({name: "formColHeadFill", label: "Column header fill", default_value: "#FFFFFF"}));
    if (showRowHeaders)
        controls.push(form.colorPicker({name: "formRowHeadFill", label: "Row header fill", default_value: "#FFFFFF"}));
    if (showRowHeaders && showColumnHeaders)
        controls.push(form.colorPicker({name: "formCornerFill", label: "Corner fill", default_value: "#FFFFFF"}));
    controls.push(form.colorPicker({name: "formCellFill", label: "Cell fill", default_value: "#FFFFFF"}));


    form.group("Border");
    var borderCollapseCtl = form.checkBox({name: "formBorderCollapse", label: "Collapse borders", default_value: true});
    var borderCollapse = borderCollapseCtl.getValue();
    controls.push(borderCollapseCtl);
    if (!borderCollapse)
    {
        controls.push(form.numericUpDown({name: "formBorderGapRows", label: "Gap between rows", default_value: 2}));
        controls.push(form.numericUpDown({name: "formBorderGapColumns", label: "Gap between columns", default_value: 2}));
    }
    controls.push(form.colorPicker({name: "formBorderColor", label: "Border color", default_value: "#FFFFFF"}));
    controls.push(form.numericUpDown({name: "formBorderWidth", label: "Border width", default_value: 1}))
} else if (output == "Stacked Column Chart with Statistical Significance")
{
    var chartType = "Column";
    if (allow_control_groups)
        form.page('Chart');
    if (allow_control_groups)
        form.group({label:'APPEARANCE', expanded: true});
    var qTemplate = form.dropBox({name: "formTemplate", label: "Use template", types: ["RItem:AppearanceTemplate"], required: false, prompt: template_prompt});
    controls.push(qTemplate);
    use_default_fonts = !isEmpty(qTemplate);

    var qBarGap = form.numericUpDown({name: "formBarGap", label: "Gap between bars", default_value: 0.50, minimum: 0.00, maximum: 1.00, increment: 0.05, prompt: "Control spacing between bars"});
    controls.push(qBarGap);

    var palettes_extra = palettes.slice(0);
    palettes_extra.push("Custom palette (color pickers)");
    palettes_extra.push("Custom palette (R output)");
    colorPaletteControls("", "", palettes_extra, controls, false);

    form.group("Annotations")
    if (!show_columncomparisons)
    {
        controls.push(form.colorPicker({name: "formAnnotArrowUpColor", label: "Color of up arrow", default_value: "#0000FF", prompt: "Color used to indicate a statistically significant increase"}));
        controls.push(form.colorPicker({name: "formAnnotArrowDownColor", label: "Color of down arrow", default_value: "#FF0000", prompt: "Color used to indicate a statistically significant decrease"}));
    }
    else
    {
        colorPaletteControls("AnnotArrow", " of arrows", palettes, controls);
    }

    controls.push(form.numericUpDown({name: "formAnnotArrowSize", label: "Arrow size", default_value: 18, prompt: "Size of arrows in pixels"}));
    controls.push(form.numericUpDown({name: "formAnnotArrowOffset", label: "Arrow offset", default_value: (1-qBarGap.getValue())*0.5, minimum: 0.0, maximum: 1.00, increment: 0.05, prompt: "Adjust positioning of arrows by increasing (right) or decreasing (left) the offset."}));

    if (allow_control_groups)
        form.group("FONT");
    var qGlobalFontDefault = form.checkBox({name: "formGlobalFontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
    controls.push(qGlobalFontDefault);
    use_default_fonts = qGlobalFontDefault.getValue();

    if (!use_default_fonts)
    {
            var qGlobalFontFamily = form.comboBox({name: "formFontFamily", label: "Global font family", alternatives: font_families, default_value: "Arial"});
            controls.push(qGlobalFontFamily);
            globalFontFamily = qGlobalFontFamily.getValue();
            var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: "Global font color", default_value: "#2C2C2C"});
            controls.push(qGlobalFontColor);
            globalFontColor = qGlobalFontColor.getValue();
            var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 7.5, increment: 0.5});
            controls.push(qGlobalFontSize);
            globalFontSize = qGlobalFontSize.getValue();
            var qFontUnits = form.comboBox({name: "formFontUnits", label: "Font units", alternatives: ["pt", "px"], default_value: "pt", prompt: "Are font sizes specified in terms of points or pixels?"});
            controls.push(qFontUnits);
    }
    var append_diff = false;
    if (!isEmpty(tb2Ctl))
    {
        if (allow_control_groups)
            form.group("Difference labels");
        var qDiffLabelShow = form.checkBox({name: "formDiffLabelShow", label: "Show differences", default_value: true, prompt: "Add labels showing the differences between the primary statistic (Table 2 - Table 1)."});
        controls.push(qDiffLabelShow);
        if (qDiffLabelShow.getValue())
        {
            var appendDiffOpt = form.checkBox({label: "Append differences to data label", name: "formDiffAppendToDataLabel", default_value: false, prompt: "Append differences to data label (if shown); otherwise differences will be shown on the right of the bars. Note if this option is selected, then difference labels are only shown when data labels are. Difference labels will be hidden when data labels are not shown, or if the primary statistic is smaller than the minimum threshold."});
            append_diff = appendDiffOpt.getValue();
            controls.push(appendDiffOpt);
            controls.push(form.checkBox({label: "Show +/- sign on differences", name: "formDiffSign", default_value: true}));
            controls.push(form.numericUpDown({label: "Number of decimals shown in differences", name: "formDiffDecimals", default_value: 0}));
            controls.push(form.numericUpDown({name: "formDiffOffset", label: "Offset", default_value: (1-qBarGap.getValue())*0.5, minimum: 0.0, maximum: 1.00, increment: 0.05, prompt: "Adjust positions fo differences data label by increasing (right) or decreasing (left) the offset."}));
            controls.push(form.textBox({label: "Prefix", name: "formDiffPrefix", required: false, prompt: "Optional text to prepend to the differences" + prefix_prompt}));
        controls.push(form.textBox({label: "Suffix", name: "formDiffSuffix", required: false, prompt: "Optional text to append to the differences"}));

            fontControls("Diff", "Differences", controls); 
        }
    }
    if (allow_control_groups)
        form.group("DATA LABELS");
    var qDatalabShow = form.checkBox({name: "formDataLabelShow", label: "Show data labels", default_value: append_diff, prompt: "Show values of data points on the chart"});
        controls.push(qDatalabShow);
    var datalabshow = qDatalabShow.getValue();
    var anydatalabelshow = datalabshow || totalsAboveCtl.getValue() || (numCatBelow > 0 && totalsBelowCtl.getValue());
    if (totalsAboveCtl.getValue())
        fontControls("TotalsAbove", "Column totals - above", controls);
    if (numCatBelow > 0 && totalsBelowCtl.getValue())
        fontControls("TotalsBelow", "Column totals - below", controls);
    if (datalabshow)
    {
        controls.push(form.numericUpDown({name: "formDataLabelThreshold", label: "Minimum threshold to show data labels", minimum: 0.0, maximum: 1.0, default_value: 0.05, increment: 0.01, prompt: "Data labels will be hidden for values which are smaller than this proportion of the total range"}));
        controls.push(form.checkBox({name: "formDataLabelCenter", label: "Vertically center data labels", default_value: true}));

        var qDatalabFontDefault = form.checkBox({name: "formDataLabelFontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qDatalabFontDefault);
        var datalabFontDefault = qDatalabFontDefault.getValue(); 
        
        if (!datalabFontDefault)
        {
            qDatalabFont = form.comboBox({name: "formDataLabelFontFamily", label: "Data label font family", alternatives: font_families, default_value: globalFontFamily});
            controls.push(qDatalabFont);
        }
        var altcols = ['Automatically', 'In a single color', 'In different colors for each series'];
        qDataLabelFontColorOpts = form.comboBox({name: "formDataLabelFontColorOptions", label: "Color data labels", alternatives: altcols, default_value: altcols[0]});
        controls.push(qDataLabelFontColorOpts);
        if (qDataLabelFontColorOpts.getValue() == "In a single color")
        {
            var qDatalabFont = form.colorPicker({name: "formDataLabelFontColor", label: !allow_control_groups ? "" : "Data label font color", default_value: globalFontColor});
            controls.push(qDatalabFont);
        }
        else if (qDataLabelFontColorOpts.getValue() == "In different colors for each series")
        {
            var qDatalabFontCol = form.textBox({name: "formDataLabelFontColor", label: "Data label font color(s):", default_value:rgbToHex(globalFontColor), prompt: "Enter a hex code or a comma-separated list of hex codes for each series in the data"});
            controls.push(qDatalabFontCol);
        }

        if (!datalabFontDefault)
        {
            qDataLabelSize = form.numericUpDown({name: "formDataLabelFontSize", label: "Data label font size", default_value: 7.5, increment: 0.5});
            controls.push(qDataLabelSize);
        }
    }

    if (anydatalabelshow)
    {
        var qDataLabelFormat = form.comboBox({name: "formDataLabelNumberType", label: "Number type", alternatives: data_label_formats, default_value: data_label_formats[0], required: true});
        var dataLabelFormat = qDataLabelFormat.getValue();
        controls.push(qDataLabelFormat);
        controls.push(form.numericUpDown({name: "formDataLabelDecimals", label: "Decimal places", increment: 1, minimum: 0, maximum: 12, default_value: 0}));
        var qDLabPrefix = form.textBox({name:'formPrefix', label: 'Custom prefix', required: false, prompt: "Optional text to prepend to the data labels"});
        controls.push(qDLabPrefix);
        var qDLabSuffix = form.textBox({name:'formSuffix', label: 'Custom suffix', required: false, prompt: "Optional text to append to the data labels"});
        controls.push(qDLabSuffix);
    }
    if (allow_control_groups)
        form.group("GRID LINES");
    controls.push(form.checkBox({name: "formShowGrid", label: "Show grid lines", default_value: true}));
    if (allow_control_groups)
        form.group("LEGEND");
    var hasleg = 1;
    var qLegShowAuto = form.comboBox({name: "formLegendShow", label: "Legend", alternatives: ["Automatic", "Show", "Hide"], default_value: "Automatic"});
    controls.push(qLegShowAuto);
    
    if (qLegShowAuto.getValue() != "Hide")
    {
        fontControls("Legend", "Legend", controls)
        var qLegOr = form.comboBox({name: "formLegendOrientation", label: "Legend orientation", alternatives: ['Vertical', 'Horizontal'], default_value: "Vertical"});
        controls.push(qLegOr);
        var legendOr = qLegOr.getValue();
        var qLegCustom = form.checkBox({name: "formLegendCustom", label: "Customize legend position", default_value: true});
        controls.push(qLegCustom);
        legendCustomPos = qLegCustom.getValue();
    
        if (legendCustomPos)
        {
            var qLegX = form.numericUpDown({name: "formLegendXPos", label: "Horizontal placement", default_value: legendOr == "Horizontal" ? 0.5: 1.02, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (far left) to 3 (far right)"});
            controls.push(qLegX);
            var qLegY = form.numericUpDown({name: "formLegendYPos", label: "Vertical placement", default_value: legendOr == "Horizontal" ? 1.10: 1.00, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (below) and 3 (above)"});
            controls.push(qLegY);
        }
    }
    var qShowAnnotFooter = form.checkBox({name: "formAnnotFooterShow", label: "Show legend for arrows", default_value: true, prompt: "Add legend for arrows to footer"});
    controls.push(qShowAnnotFooter);
    if (qShowAnnotFooter.getValue())
        controls.push(form.comboBox({name: "formAnnotLegendSep", label: "Separate legend using", alternatives: ["Centered dot", "New line", "Space"], default_value: "Centered dot"}));


    if (allow_control_groups)
        form.group('Title');
    formattedTextControls("Title", "Title", controls, 12, "", true);
    formattedTextControls("Subtitle", "Subtitle", controls, 9, "");
    formattedTextControls("Footer", "Footer", controls, 6, "Optional footer. Leave blank to show default text", true, true, 150);
    controls.push(form.comboBox({name: "formFooterAlign", label: "Footer alignment", alternatives: ["Center", "Left", "Right"], default_value: "Center"})); 

    form.group("CATEGORIES (X) AXIS");
    var axisPrefix =  "Axis";
    if (!allow_control_groups)
        axisPrefix = categoriesAxisLabel.charAt(0).toUpperCase() + categoriesAxisLabel.substr(1).toLowerCase();
    var labelShowX = "Show " + axisPrefix.toLowerCase();
    
    controls.push(form.textBox({name: "formCategoriesMin", label: "Minimum value", required: false, prompt: "Leave blank to determine automatically from input data. Otherwise, enter a numeric value (no prefix or suffix) for a numeric axis; a date-string for a date axis; or a 0-based index for a categorical axis."}));
    controls.push(form.textBox({name: "formCategoriesMax", label: "Maximum value", required: false, prompt: "Leave blank to determine automatically from input data. Otherwise, enter a numeric value (no prefix or suffix) for a numeric axis; a date-string for a date axis; or a 0-based index for a categorical axis."}));
    var qXGridWidth = form.numericUpDown({name: "formCategoriesGridWidth", label: "Grid line width", minimum: 0, default_value: (chartType == "Radar" || chartType == "Scatter") ? 1 : 0});
    controls.push(qXGridWidth);
    if (qXGridWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formCategoriesGridColor", label: "Grid line color", default_value: "#E1E1E1"}));
    var qXLineWidth = form.numericUpDown({name: "formCategoriesLineWidth", label: "Axis line width", minimum: 0, default_value: 0});
    controls.push(qXLineWidth);
    if (qXLineWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formCategoriesLineColor", label: "Axis line color", default_value: "#000000"}));
    var qShowXTitle = form.checkBox({name: "formShowCategoriesTitle", label: labelShowX + " title", default_value: true});
    var showCategoriesTitle = qShowXTitle.getValue();
    controls.push(qShowXTitle);
    if (showCategoriesTitle)
        formattedTextControls("CategoriesTitle", axisPrefix + " title", controls, 9, "Leave blank to read axis title from input data", true)
    var qShowX = form.checkBox({name: "formCategoriesAxisShow", label: labelShowX, default_value: true});
    controls.push(qShowX);
    var showX = qShowX.getValue();

    
    // Allow axis format to be changed even if it is not shown
    // e.g. switching between date and categorical axis.
    numberFormatControls("Categories", axisPrefix, controls, categories_number_formats, true, showX)
    if (showX)
    {
        controls.push(form.numericUpDown({name: "formCategoriesTickInterval", label: axisPrefix + " tick interval", default_value: 1, increment: 1, minimum: 1, maximum: 1000}));
        fontControls("CategoriesTick", axisPrefix + " label", controls, 7.5, adjustColor = true); 
        controls.push(form.comboBox({name:"formCategoriesTickHorizAlign", label: "Horizontal alignment", alternatives: ["Default", "Left", "Center", "Right"], default_value: "Default"})); 
        controls.push(form.comboBox({name: "formCategoriesTickAngle", label: axisPrefix + " label orientation", alternatives: ["Automatic", "Horizontal", "Vertical", "Diagonal"], default_value: "Automatic"}));
        var qXWrap = form.checkBox({name: "formLabelWrap", label: "Wrap " + axisPrefix.toLowerCase() + " label", default_value: true});
        var xWrapOpt = qXWrap.getValue();
        controls.push(qXWrap);
        if (xWrapOpt)
            controls.push(form.numericUpDown({name: "formLabelWrapNchar", label: axisPrefix + " label width (in characters)", default_value: 21, minimum: 5, increment: 5, maximum: 1000}));
    }

    form.group("VALUES (Y) AXIS");
    var axisPrefix = "Axis";
    if (!allow_control_groups)
        axisPrefix = valuesAxisLabel.charAt(0).toUpperCase() + valuesAxisLabel.substr(1).toLowerCase();
    var labelShowY = "Show " + axisPrefix.toLowerCase();
    controls.push(form.textBox({name: "formValuesMin", label: "Minimum value", required: false, prompt: "Leave blank to determine automatically from input data."}));
    controls.push(form.textBox({name: "formValuesMax", label: "Maximum value", required: false, prompt: "Leave blank to determine automatically from input data."}));

    var qYGridWidth = form.numericUpDown({name: "formValuesGridWidth", label: "Grid line width", minimum: 0, default_value: 1});
    controls.push(qYGridWidth);
    var showYgrid = qYGridWidth.getValue() > 0;
    if (showYgrid)
        controls.push(form.colorPicker({name: "formValuesGridColor", label: "Grid line color", default_value: "#E1E1E1"}));
    var qYLineWidth = form.numericUpDown({name: "formValuesLineWidth", label: "Axis line width", minimum: 0, default_value: 0});
    controls.push(qYLineWidth);
    if (qYLineWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formValuesLineColor", label: "Axis line color", default_value: "#000000"}));
    var qYZeroLineWidth = form.numericUpDown({name: "formValuesZeroLineWidth", label: "Axis zero line width", minimum: 0, default_value: 3, prompt: "Line width of line at y = 0"});
    controls.push(qYZeroLineWidth);
    if (qYZeroLineWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formValuesZeroLineColor", label: "Axis zero line color", default_value: "#000000"}));
    var qShowValuesTitle = form.checkBox({name: "formShowValuesTitle", label: labelShowY + " title", default_value: true});
    var showValuesTitle = qShowValuesTitle.getValue();
    controls.push(qShowValuesTitle);
    if (showValuesTitle)
        formattedTextControls("ValuesTitle", axisPrefix + " title", controls, 9, "Leave blank to read axis title from input data", true);

    var qShowY = form.checkBox({name: "formValuesAxisShow", label: labelShowY, default_value: numCatBelow == 0});
    controls.push(qShowY);
    var showY = qShowY.getValue();

    // Allow formatting to be changed even if axis is not show
    // use for e.g. switching between date and categorical axis
    var yNumberType = numberFormatControls("Values", axisPrefix, controls, values_number_formats, true, showY)
    if (showY) 
        fontControls("ValuesTick", axisPrefix + " label", controls);



    if (allow_control_groups)
        form.group("HOVER");
    var qShowHover = form.checkBox({name: "formHoverShow", label: "Show hover text", default_value: true});
    var showHover = qShowHover.getValue();
    controls.push(qShowHover);
    if (showHover)
    {
        var hover_default_format = (typeof dataLabelFormat != 'undefined') ? dataLabelFormat : hover_number_formats[0];
        var qHoverNumberType = form.comboBox({name: "formHoverNumberType", label: "Hovertext number type", alternatives: hover_number_formats, default_value: hover_default_format, required: true});
        var hoverNumberType = qHoverNumberType.getValue();
        controls.push(qHoverNumberType);
        if (hoverNumberType == "Date/Time")
        {
            var qHoverDate = form.comboBox({name: "formHoverDateType", label: "Date type", alternatives: date_formats, default_value: date_formats[0], required: true});
            hoverNumberType = qHoverDate.getValue();
            controls.push(qHoverDate);
        }
        var qHoverCustom = null;
        if (hoverNumberType == "Custom")
            qHoverCustom = form.textBox({name: "formHoverNumberCustom", label: "Custom code", default_value: "%d %b %y", required: true, prompt: "D3 formatting code, e.g. ,.2%"});
        else if (hoverNumberType == 'Currency')
            qHoverCustom = form.textBox({name: "formHoverCurrency", label: "Currency symbol", default_value: "$", required: true}); 
        else if (hoverNumberType == 'Number' && chartType != "Venn" && chartType != "Stream")
            qHoverCustom = form.checkBox({name: "formHoverSeparateThousands", label: "Separate thousands by comma", default_value: true});
        if (qHoverCustom != null)
            controls.push(qHoverCustom);
        var qHoverDecimals = form.numericUpDown({name:"formHoverDecimals", label: hoverNumberType == "Metric unit suffix" ? "Significant digits": "Decimal places", minimum: hoverNumberType == "Metric unit suffix" ? 1 : 0, required: false});
        controls.push(qHoverDecimals);
        
        var qHoverPrefix = form.textBox({name:'formHoverValuesPrefix', label: 'Custom prefix', required: false, prompt: "Optional text to prepend to hovertext values"});
        controls.push(qHoverPrefix);
        var qHoverSuffix = form.textBox({name:'formHoverValuesSuffix', label: 'Custom suffix', required: false, prompt: "Optional text to append to hovertext values"});
        controls.push(qHoverSuffix);
        var qHoverFontDefault = form.checkBox({name: "formHoverFontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qHoverFontDefault);
        var hoverFontDefault = qHoverFontDefault.getValue(); 
        var qHoverFontFamily = form.comboBox({name: "formHoverFontFamily", label: "Hovertext font family", alternatives: font_families, default_value: globalFontFamily});
        controls.push(qHoverFontFamily);
        var qHoverFontSize = form.numericUpDown({name: "formHoverFontSize", label: "Hovertext font size", default_value: 8.5}); 
        controls.push(qHoverFontSize);
    }

    if (allow_control_groups)
        form.group("MARGINS");
    var qMarginContol = false; 
    qCustomMargin = form.checkBox({name: "formCustomMargin", label: "Customize margins", default_value: false, prompt: "Select to manually specify margin sizes in pixels"});
    controls.push(qCustomMargin);
    if (qCustomMargin.getValue())
    {
        controls.push(form.checkBox({name: "formMarginAutoexpand", label: "Automatically expand margins", default_value: false, prompt: "Margins are automatically expanded if text in the axis labels or legend is too long to fit in margins"}));
        controls.push(form.numericUpDown({name: "formMarginTop", label: "Top", default_value: 30, increment: 1, minimum: 0, maximum: 1000}));
        controls.push(form.numericUpDown({name: "formMarginLeft", label: "Left", default_value: 80, increment: 1, minimum: 0, maximum: 1000}));
        controls.push(form.numericUpDown({name: "formMarginBottom", label: "Bottom", default_value: 50, increment: 1, minimum: 0, maximum: 1000}));
        controls.push(form.numericUpDown({name: "formMarginRight", label: "Right", default_value: 40, increment: 1, minimum: 0, maximum: 1000}));
    }

    if (allow_control_groups)
        form.group("BACKGROUND")
    var qBg = form.checkBox({name: "formTransparent", label: "Transparent background", default_value: true, prompt: "Turn off to manually adjust color and opacity"}); 
    controls.push(qBg);
    if (!qBg.getValue())
    {
        controls.push(form.colorPicker({name: "formBgColor", label: "Background color", default_value: "#FFFFFF"}));
        controls.push(form.numericUpDown({name: "formBgOpacity", label: "Background opacity", minimum: 0.0, maximum: 1.0, increment: 0.1, default_value: 1.0}));
    }
}
form.setInputControls(controls);
# VERSION 1.0.3
library(flipTables)
library(flipChartBasics)
library(flipChart)
library(flipAnalysisOfVariance)

# Set up stuff for template colors etc
# Brand colors should work for both column bars and arrows

cutoffs <- NULL
if (formOutput == "Table of Differences")
{
    if (!exists("formNumLevels"))
        formNumLevels <- 1

    cutoffs <- rep(NA, formNumLevels)
    colors.ub <- rep(NA, formNumLevels)
    colors.lb <- rep(NA, formNumLevels)
    colors.ub.border <- rep(NA, formNumLevels)
    colors.lb.border <- rep(NA, formNumLevels)
    for (i in 1:formNumLevels)
    {
        cutoffs[i] <- get0(paste0("formCondCutoff", i), ifnotfound = NA)
        colors.ub[i] <- get0(paste0("formCondShadeUB", i), ifnotfound = NA)
        colors.lb[i] <- get0(paste0("formCondShadeLB", i), ifnotfound = NA)
        colors.lb.border[i] <- get0(paste0("formCondShadeLBBorder", i), ifnotfound = NA)
        colors.ub.border[i] <- get0(paste0("formCondShadeUBBorder", i), ifnotfound = NA)
    }
}

if (formOutput == "Table of Differences" || !is.null(formTable2))
    table.of.diffs <- TableOfDifferences(formTable1, formTable2,
                  output = if (formOutput == "Stacked Column Chart with Statistical Significance") "qtable" else "widget",
                  proportions.test = get0("QSettings$StatisticalAssumptions$ProportionTest", ifnotfound = "Nonparametric"),
                  proportions.bessel = get0("QSettings$StatisticalAssumptions$ProportionBessel", ifnotfound = FALSE),
                  means.test = get0("QSettings$StatisticalAssumptions$MeansTest", ifnotfound = "tTest"),
                  means.bessel = get0("QSettings$StatisticalAssumptions$MeanBessel", ifnotfound = TRUE),
                  design.effect.constant = get0("QSettings$StatisticalAssumptions$DesignEffectConstant", ifnotfound=1),
                  show = formShowVal,
                  cond.shade = formCondShadeType,
                  cond.shade.cutoffs = cutoffs,
                  cond.shade.ub.colors = colors.ub,
                  cond.shade.lb.colors = colors.lb,
                  cond.shade.ub.bordercolors = colors.ub.border,
                  cond.shade.lb.bordercolors = colors.lb.border,
                  cond.box.radius = get0("formCondBoxRadius"),
                  cond.box.borderwidth = get0("formCondBoxWidth"),
                  cond.box.padding.top = get0("formCondBoxPaddingTop"),
                  cond.box.padding.bottom = get0("formCondBoxPaddingBottom"),
                  cond.box.padding.left = get0("formCondBoxPaddingLeft"),
                  cond.box.padding.right = get0("formCondBoxPaddingRight"),
                  cell.fill = formCellFill,
                  font.color = formFontColor,
                  font.size = formFontSize,
                  font.unit = formFontUnits,
                  font.family = formFontFamily,
                  legend.show = formLegendShow,
                  legend.font.family = get0("formLegendFontFamily"),
                  legend.font.color = get0("formLegendFontColor"),
                  legend.font.size = get0("formLegendFontSize"),
                  format.statistic.decimals = get0("formPStatDecimals"),
                  format.statistic.prefix = get0("formPStatPrefix"),
                  format.statistic.suffix = get0("formPStatSuffix"),
                  format.statistic.font.family = get0("formPStatFontFamily"),
                  format.statistic.font.color = get0("formPStatFontColor"),
                  format.statistic.font.autocolor = get0("formPStatFontAutocolor"),
                  format.statistic.font.size = get0("formPStatFontSize"),
                  format.difference.sign = get0("formDiffSign"),
                  format.difference.decimals = get0("formDiffDecimals"),
                  format.difference.prefix = get0("formDiffPrefix"),
                  format.difference.suffix = get0("formDiffSuffix"),
                  format.difference.font.family = get0("formDiffFontFamily"),
                  format.difference.font.color = get0("formDiffFontColor"),
                  format.difference.font.autocolor = get0("formDiffFontAutocolor"),
                  format.difference.font.size = get0("formDiffFontSize"),
                  show.row.header = formRowHeadersShow,
                  row.header.fill = get0("formRowHeadFill"),
                  row.header.align.horizontal = get0("formRowHeadersAlignHoriz"),
                  row.header.pad = get0("formRowHeadersPad", ifnotfound = 0),
                  row.header.font.family = get0("formRowHeadersFontFamily"),
                  row.header.font.color = get0("formRowHeadersFontColor"),
                  row.header.font.size = get0("formRowHeadersFontSize"),
                  row.header.font.weight = get0("formRowHeadersFontWeight"), 
                  show.col.header = formColumnHeadersShow,
                  col.header.fill = get0("formColHeadFill"), 
                  col.header.font.family = get0("formColumnHeadersFontFamily"),
                  col.header.font.color = get0("formColumnHeadersFontColor"),
                  col.header.font.size = get0("formColumnHeadersFontSize"),
                  col.header.font.weight = get0("formColumnHeadersFontWeight"),
                  col.widths = formColumnWidths,
                  corner.fill = get0("formCornerFill"),
                  row.height = get0("formRowHeight"),
                  border.color = formBorderColor,
                  border.width = formBorderWidth,
                  col.header.border.width = if (isTRUE(get0("formColumnHeadersBorder"))) formBorderWidth else 0,
                  row.header.border.width = if (isTRUE(get0("formRowHeadersBorder"))) formBorderWidth else 0,
                  border.collapse = formBorderCollapse,
                  border.row.gap = get0("formBorderGapRows"),
                  border.column.gap = get0("formBorderGapColumns"),
                  row.names.to.remove = if (formOutput == "Table of Differences") formIgnoreRows else NULL,
                  column.names.to.remove = if (formOutput == "Table of Differences") formIgnoreColumns else NULL)

if (formOutput == "Stacked Column Chart with Statistical Significance")
{
    dat <- if (is.null(formTable2)) formTable1 else table.of.diffs

    # Set defaults if no template
    template <- get0("formTemplate")
    if (is.null(template))
    {
        template <- list(colors = "Default colors", global.font = list(family = "Arial", 
        color = "#2C2C2C", size = 7.5, units = "pt"), fonts = list(
        `Data labels` = list(family = "Arial", color = "#2C2C2C", size = 7.5), 
        Legend = list(family = "Arial", color = "#2C2C2C", size = 7.5), 
        Title = list(family = "Arial", color = "#2C2C2C", size = 12), 
        Subtitle = list(family = "Arial", color = "#2C2C2C", size = 9), 
        Footer = list(family = "Arial", color = "#2C2C2C", size = 6), 
        `Categories axis title` = list(family = "Arial", color = "#2C2C2C", size = 9), 
        `Categories axis tick labels` = list(family = "Arial", color = "#2C2C2C", size = 7.5), 
        `Values axis title` = list(family = "Arial", color = "#2C2C2C", size = 9), 
        `Values axis tick labels` = list(family = "Arial", color = "#2C2C2C", size = 7.5), 
        `Panel title` = list(family = "Arial", color = "#2C2C2C", size = 9), 
        `Hover text` = list(family = "Arial", color = "#2C2C2C", size = 8.625)))
    }
    pn <- PrepareNumbers(categories.format.list = list(get0("formCategoriesNumberType"), get0("formCategoriesDateType"), get0("formCategoriesNumberCustom"), get0("formCategoriesSeparateThousands"), get0("formCategoriesDecimals")), values.format.list = list(get0("formValuesNumberType"), get0("formValuesDateType"), get0("formValuesNumberCustom"), get0("formValuesSeparateThousands"), get0("formValuesDecimals")), hover.format.list = list(get0("formHoverNumberType"), get0("formHoverDateType"), get0("formHoverNumberCustom"), get0("formHoverSeparateThousands"), get0("formHoverDecimals")), data.labels.format.list = list(get0("formDataLabelNumberType"), get0("formDataLabelDateType"), get0("formDataLabelCustom"), get0("formDataLabelSeparateThousands"), get0("formDataLabelDecimals")), as.percentages = FALSE)
   
    # Set up color vectors
    dat2 <- ConvertQTableToArray(dat)
    dat2 <- RemoveRowsAndOrColumns(dat2, formIgnoreRows, formIgnoreColumns)
    if (formTranspose)
        dat2 <- aperm(dat2, c(2, 1, 3))
    if (formColumnsReverse)
        dat2 <- dat2[,NCOL(dat2):1,,drop = FALSE]

    custom.palette <- get0("formCustomPalette")
    if (exists("formPalette"))
    {
        if (formPalette == "Custom palette (R output)")
        {
            custom.palette <- get0("formCustomPaletteROutput")
        }
        if (formPalette == "Custom palette (color pickers)")
        {
            custom.palette <- rep(NA, 12)
            for (i in 1:12)
                custom.palette[i] <- get0(paste0("formCustomPalette", i))
        }
    }
    series.colors <- GetVectorOfColors(template, dat2, NULL, "Column",
        palette = get0("formPalette"), palette.custom.color = get0("formCustomColor"), 
        palette.custom.gradient.start = get0("formCustomGradientStart"),
        palette.custom.gradient.end = get0("formCustomGradientEnd"), 
        palette.custom.palette = custom.palette) 
    arrow.colors <- if (exists("formAnnotArrowUpColor")) c(formAnnotArrowUpColor, formAnnotArrowDownColor) else  GetVectorOfColors(template, dat,
        NULL, "Column", palette = get0("formAnnotArrowPalette"), 
        palette.custom.color = get0("formAnnotArrowCustomColor"), 
        palette.custom.gradient.start = get0("formAnnotArrowCustomGradientStart"), 
        palette.custom.gradient.end = get0("formAnnotArrowCustomGradientEnd"), 
        palette.custom.palette = get0("formAnnotArrowCustomPalette"))

    chart.with.annot <- CChart("StackedColumnWithStatisticalSignificance", 
        x = dat,
        num.categories.below.axis = formNumCatBelowAxis,
        transpose = formTranspose,
        reverse.series.order = formColumnsReverse,
        row.names.to.remove = formIgnoreRows,
        column.names.to.remove = formIgnoreColumns,
        colors = series.colors,
        global.font.family = get0("formFontFamily", ifnotfound=template$global.font$family),
        global.font.color = get0("formFontColor", ifnotfound=template$global.font$color),
        annot.footer.show = get0("formAnnotFooterShow", ifnotfound=TRUE),
        annot.arrow.size = get0("formAnnotArrowSize"),
        annot.arrow.colors = arrow.colors,
        annot.arrow.offset = get0("formAnnotArrowOffset"),
        annot.alpha = 0.05, # This should reflect table - use QSettings?
        annot.legend.sep = switch(get0("formAnnotLegendSep", ifnotfound=NA), "Centered dot" = " &#183; ", "New line" = "<br>", Space = "&nbsp;", "!"),
        annot.differences.show = get0("formDiffLabelShow", ifnotfound = FALSE),
        append.annot.differences.to.datalabel = get0("formDiffAppendToDataLabel", ifnotfound = FALSE),
        annot.differences.decimals = get0("formDiffDecimals"),
        annot.differences.sign.show = get0("formDiffSign", ifnotfound = TRUE),
        annot.differences.prefix = get0("formDiffPrefix"),
        annot.differences.suffix = get0("formDiffSuffix"), 
        annot.differences.font.size = get0("formDiffFontSize", ifnotfound = template$fonts$`Data labels`$size),
        annot.differences.font.family = get0("formDiffFontFamily", ifnotfound = template$fonts$`Data labels`$family),
        annot.differences.font.color = get0("formDiffFontColor", ifnotfound = template$fonts$`Data labels`$color),
        annot.differences.offset = get0("formDiffOffset"),
        column.totals.above.show = get0("formShowTotalsAbove", ifnotfound = FALSE),
        column.totals.above.font.size = get0("formTotalsAboveFontSize", ifnotfound = template$fonts$`Data labels`$size),
        column.totals.above.font.family = get0("formTotalsAboveFontFamily", ifnotfound = template$fonts$`Data labels`$family),
        column.totals.above.font.color = get0("formTotalsAboveFontColor", ifnotfound = template$fonts$`Data labels`$color),
        column.totals.below.show = get0("formShowTotalsBelow", ifnotfound = FALSE),
        column.totals.below.font.size = get0("formTotalsBelowFontSize", ifnotfound = template$fonts$`Data labels`$size),
        column.totals.below.font.family = get0("formTotalsBelowFontFamily", ifnotfound = template$fonts$`Data labels`$family),
        column.totals.below.font.color = get0("formTotalsBelowFontColor", ifnotfound = template$fonts$`Data labels`$color),
        bar.gap = formBarGap,
        # Chart: DATA LABELS
        data.label.show = get0("formDataLabelShow", ifnotfound = FALSE),
        data.label.threshold = get0("formDataLabelThreshold"),
        data.label.centered = get0("formDataLabelCenter", ifnotfound = TRUE),
        data.label.format = pn$data.labels.number.format,
        data.label.font.size = get0("formDataLabelFontSize", ifnotfound = template$fonts$`Data labels`$size),
        data.label.font.family = get0("formDataLabelFontFamily", ifnotfound = template$fonts$`Data labels`$family),
        data.label.font.color = get0("formDataLabelFontColor", ifnotfound = template$fonts$`Data labels`$color),
        data.label.font.autocolor = exists("formDataLabelFontColorOptions") && formDataLabelFontColorOptions == "Automatically",
        data.label.prefix = get0("formPrefix", ifnotfound=""),
        data.label.suffix = get0("formSuffix", ifnotfound=""),
        #Chart: GRIDLINES
        grid.show = get0("formShowGrid", ifnotfound=TRUE),
        values.line.width = get0("formValuesLineWidth", ifnotfound = 0),
        values.line.color = get0("formValuesLineColor", ifnotfound = "#000000"),
        values.zero = sum(get0("formValuesZeroLineWidth")) > 0,
        values.zero.line.width = get0("formValuesZeroLineWidth", ifnotfound = 0),
        values.zero.line.color = get0("formValuesZeroLineColor", ifnotfound = "#000000"),
        values.grid.width = get0("formValuesGridWidth", ifnotfound = 0),
        values.grid.color = get0("formValuesGridColor", ifnotfound = "#E1E1E1"),
        categories.line.width = get0("formCategoriesLineWidth", ifnotfound = 0),
        categories.line.color = get0("formCategoriesLineColor", ifnotfound = "#000000"),
        categories.grid.width = get0("formCategoriesGridWidth", ifnotfound = 0),
        categories.grid.color = get0("formCategoriesGridColor", ifnotfound = "#E1E1E1"),
        # Chart: LEGEND
        legend.show = get0("formLegendShow", ifnotfound = FALSE),
        legend.orientation = get0("formLegendOrientation", ifnotfound = "Vertical"),
        legend.title = get0("formLegendTitle"),
        legend.font.family = get0("formLegendFontFamily", ifnotfound = template$fonts$Legend$family),
        legend.font.color = get0("formLegendFontColor", ifnotfound = template$fonts$Legend$color),
        legend.font.size = get0("formLegendFontSize", ifnotfound = template$fonts$Legend$size),
        legend.x.position = get0("formLegendXPos"),
        legend.y.position = get0("formLegendYPos"),
        # Chart: TITLE
        title = paste0("", if (sum(nchar(get0("formTitle"))) > 0) formTitle else ""),
        title.font.family = get0("formTitleFontFamily", ifnotfound = template$fonts$`Title`$family),
        title.font.color = get0("formTitleFontColor", ifnotfound = template$fonts$`Title`$color),
        title.font.size = get0("formTitleFontSize", ifnotfound = sum(template$fonts$`Title`$size)),
        subtitle = get0("formSubtitle", ifnotfound=""),
        subtitle.font.family = get0("formSubtitleFontFamily", ifnotfound = template$fonts$Subtitle$family),
        subtitle.font.color = get0("formSubtitleFontColor", ifnotfound = template$fonts$Subtitle$color),
        subtitle.font.size = get0("formSubtitleFontSize", ifnotfound = template$fonts$Subtitle$size),
        footer = paste0("", if (sum(nchar(get0("formFooter"))) > 0) formFooter else ""),
        footer.font.family = get0("formFooterFontFamily", ifnotfound = template$fonts$Footer$family),
        footer.font.color = get0("formFooterFontColor", ifnotfound = template$fonts$Footer$color),
        footer.font.size = get0("formFooterFontSize", ifnotfound = template$fonts$Footer$size),
        footer.wrap = get0("formFooterWrap", ifnotfound=FALSE),
        footer.wrap.nchar = get0("formFooterWrapNchar"),
        footer.align = tolower(get0("formFooterAlign")),
        #Chart: CATEGORIES_AXIS
        categories.bounds.minimum = get0("formCategoriesMin"), 
        categories.bounds.maximum = get0("formCategoriesMax"), 
        categories.axis.show = get0("formCategoriesAxisShow", ifnotfound=FALSE),
        categories.tick.show = get0("formCategoriesAxisShow", ifnotfound=FALSE),
        categories.tick.format = pn$categories.number.format,
        categories.tick.prefix = paste0("", get0("formCategoriesPrefix"), get0("formCategoriesCurrency")),    # currency is just another prefix
        categories.tick.suffix = get0("formCategoriesSuffix", ifnotfound=""),
        categories.tick.interval = get0("formCategoriesTickInterval", ifnotfound=0),
        categories.tick.units = get0("formCategoriesTickUnits"),
        categories.title = paste0("", if (sum(nchar(get0("formCategoriesTitle", ifnotfound = " "))) > 0) get0("formCategoriesTitle", ifnotfound = " ")), # else pd$categories.title),
        categories.title.font.family = get0("formCategoriesTitleFontFamily", ifnotfound = template$fonts$`Categories axis title`$family),
        categories.title.font.color = get0("formCategoriesTitleFontColor", ifnotfound = template$fonts$`Categories axis title`$color),
        categories.title.font.size = get0("formCategoriesTitleFontSize", ifnotfound = sum(template$fonts$`Categories axis title`$size)), 
        categories.tick.font.family = get0("formCategoriesTickFontFamily", ifnotfound = template$fonts$`Categories axis tick labels`$family),
        categories.tick.font.color = get0("formCategoriesTickFontColor", ifnotfound = template$fonts$`Categories axis tick labels`$color),
        categories.tick.font.size = get0("formCategoriesTickFontSize", ifnotfound = sum(template$fonts$`Categories axis tick labels`$size)),
        categories.tick.angle = if (!exists("formCategoriesTickAngle")) NULL else switch(get0("formCategoriesTickAngle"), Automatic=NULL, Horizontal=0, Vertical=90, Diagonal=45),
        categories.tick.label.wrap = get0("formLabelWrap", ifnotfound=FALSE),
        categories.tick.label.wrap.nchar = get0("formLabelWrapNchar", ifnotfound=100),
        #Chart: VALUES_AXIS
        values.bounds.minimum = get0("formValuesMin"), 
        values.bounds.maximum = get0("formValuesMax"), 
        values.axis.show = get0("formValuesAxisShow", ifnotfound=FALSE),
        values.tick.show = get0("formValuesAxisShow", ifnotfound=FALSE),
        values.tick.format = pn$values.number.format,
        values.tick.prefix = paste0("", get0("formValuesPrefix"), get0("formValuesCurrency")),    # currency is just another prefix
        values.tick.suffix = get0("formValuesSuffix", ifnotfound=""),
        values.title =  paste0("", if (sum(nchar(get0("formValuesTitle", ifnotfound=" "))) > 0) get0("formValuesTitle", ifnotfound = " ")),
        values.title.font.family = get0("formValuesTitleFontFamily", ifnotfound = template$fonts$`Values axis title`$family),
        values.title.font.color = get0("formValuesTitleFontColor", ifnotfound = template$fonts$`Values axis title`$color),
        values.title.font.size = get0("formValuesTitleFontSize", ifnotfound = sum(template$fonts$`Values axis title`$size)),
        values.number.ticks = get0("formValuesNumberTicks"),
        values.tick.font.size = get0("formValuesTickFontSize", ifnotfound = sum(template$fonts$`Values axis tick labels`$size)),
        values.tick.font.family = get0("formValuesTickFontFamily", ifnotfound = template$fonts$`Values axis tick labels`$family),
        values.tick.font.color = get0("formValuesTickFontColor", ifnotfound = template$fonts$`Values axis tick labels`$color),
        # Chart: HOVER
        values.hovertext.format = pn$hover.number.format,
        values.hovertext.prefix = paste0("", get0("formHoverValuesPrefix"), get0("formHoverCurrency")),    # currency is just another prefix
        values.hovertext.suffix = get0("formHoverValuesSuffix", ifnotfound = ""),
        hovertext.font.family = get0("formHoverFontFamily", ifnotfound = template$fonts$`Hover text`$family),
        hovertext.font.size = get0("formHoverFontSize", ifnotfound = template$fonts$`Hover text`$size),
        hovertext.font.color = if (isTRUE(get0("formHoverAutoFontColor"))) NULL else get0("formHoverFontColor", ifnotfound = template$fonts$`Hover text`$color),
        tooltip.show = get0("formHoverShow", ifnotfound = TRUE),
        # Chart: MARGINS
        margin.top = get0("formMarginTop"),
        margin.left = get0("formMarginLeft"),
        margin.bottom = get0("formMarginBottom"),
        margin.right = get0("formMarginRight"),
        margin.autoexpand = get0("formMarginAutoexpand", ifnotfound = TRUE),
        # General arguments
        font.units = get0("formFontUnits", ifnotfound = "pt"),
        background.fill.color = get0("formBgColor", ifnotfound = "transparent"),
        background.fill.opacity = get0("formBgOpacity", ifnotfound = 1.0),
        warn.if.no.match = FALSE,
        append.data = TRUE)
}
# chart.with.annot
output.with.annotations <- if (formOutput == "Table of Differences") table.of.diffs else chart.with.annot