Visualization - Column - Stacked Column with Custom Tests

From Q
Jump to navigation Jump to search
VizIcon Stacked Column Chart With Statistical Significance.svg

Creates a Stacked Column Chart automatically annotated with statistical significance.

Examples

Object Inspector Options

The following is an explanation of the options available in the Object Inspector for this specific visualization. Refer to Visualization Options for general chart formatting options.

INPUTS
Output select the type of output to display from this analysis. It can one of Table of Differences or Stacked Column Chart with Statistical Significance.
Table 1 A Q Table containing the data to be plotted. The size of the bars in the column chart are determined by the primary statistic in the input table, and the chart will also show arrows indicating which values are statistically significant in the same way as the Q Table. If Table 1 contains "Column Comparisons", then up-arrows indicating that values are significantly increased relative to the same values in other columns (but the same row) are shown. Otherwise, if Table 1 contains the z-Statistics, then up and down arrows reflecting the arrows in the table are shown. If Table 2 is provided, however, the difference of the two tables is shown instead of using the cell statistics in Table 1.
Table 2 If a second table is provided then, it is expected to have the same primary statistics and row/column names as Table 1, as well as the appropriate sample size and standard error cell statistics. A t-Test will be performed comparing Table 1 and Table 2 and arrows reflecting the z-Statistics from this test is shown in the same way as Tables - Table of Differences. The size of the bars in the column chart will reflect the primary statistic in Table 2.
Number of categories below axis An integer, indicating the number of categories to be shown below the y = 0 (values axis zero) line.
Column totals - above If selected, the sum of the categories above values axis zero line is shown above each bar. If column totals are shown then font controls can be found in the CHART > DATA LABELS group. The format of the column totals will be the same as the format of the data labels.
Column totals - below If selected, the sum of the categories below values axis zero line is shown below each bar. If column totals are shown then font controls can be found in the CHART > DATA LABELS group.
APPEARANCE
Gap between bars Specify the amount of space between bars as proportion of the available space. Values can range from 0 (no gaps) to 0.99 (bars are shown as lines).
Vertically center data labels If selected, data labels in stacked column charts are placed in the middle of the column; the alternative is to have the data labels placed at the top of the column.
ANNOTATION
Arrow colors When the chart is showing z-Statistics or comparing the difference between two tables, the two color pickers are shown to control the color of arrows indicating a significant increase or decrease. However, for if the chart is shown Column Comparisons, then a color palette selected, with a color for each column.
Arrow size The size of the arrows used to show statistical significance in pixels.
Arrow offset The position of the arrows as a proportion of the space between bars. This automatically based on the Gap between bars, however, it is possible to move the arrows closer to the bars (left) by reducing the offset or further away (right) by increasing the offset. If the arrows overlap with the bars (especially likely if there many column comparisons in the table), it better to increase the gap between bars, or increase the total width of the widget.
DIFFERENCE LABELS
This group of controls is only shown if both Table 1 and Table 2 are provided as the input data. They control the appearance of the labels showing the difference between the primary statistic of Table 1 and Table 2.
Append differences to data label Option to show difference labels next to the data labels inside the bars instead of to the right of the bars (default). 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.
LEGEND
Show legend for arrows If selected, a legend for the arrows used to indicate statistical significance is shown in the footer of the text. More controls to adjust the font are available in TITLE > Footer font family.
Separate legend using one of 'New line', 'Centered dot', 'Space'. The character used to separate the legend for each type of arrow in the foorter.


Code

var default_output = "Stacked Column Chart with Statistical Significance";

// 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 'Visualization > 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 = !!Q.GetAvailableFontNames ? Q.GetAvailableFontNames() : ["Arial", "Arial Black", "Comic Sans MS",  "Courier New", "Georgia", "Impact", 
                 "Open Sans", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana"];
var use_default_fonts = false;
const DEFAULT_FONT_COLOR = "#444444";
const DEFAULT_FONT_FAMILY = "Open Sans";
const DEFAULT_GRID_COLOR = "#D2D8E1";
const DEFAULT_GRID_WIDTH = 0.5;
const DEFAULT_AXIS_COLOR = "#1C283B";
const DEFAULT_AXIS_WIDTH = 1.0;

// * Palette alternatives *
palettes = ["Default or template settings", "Office 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 = 8, 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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
            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,array", "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 != 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: "formHidePercent", label: "Hide percent symbol", prompt: "Percentage data will be treated as if it was just numeric data", default_value: false}));
    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: DEFAULT_FONT_FAMILY, alternatives: font_families, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
    controls.push(fontFamilyCtl);
    var fontFamily = fontFamilyCtl.getValue();
    var fontSizeCtl = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 9});
    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: DEFAULT_FONT_COLOR});
    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", required: false}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        controls.push(form.numericUpDown({name: "formPStatFontSize", label: "Font size of primary statistic", default_value: fontSize, increment: 0.5}));
        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", required: false}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        controls.push(form.numericUpDown({name: "formDiffFontSize", label: "Font size of differences", default_value: fontSize, increment: 0.5}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        controls.push(form.numericUpDown({name: "formLegendFontSize", label: "Font size", default_value: 8, increment: 0.5}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        controls.push(form.numericUpDown({name: "formColumnHeadersFontSize", label: "Font size", default_value: fontSize, increment: 0.5}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        controls.push(form.numericUpDown({name: "formRowHeadersFontSize", label: "Font size", default_value: fontSize, increment: 0.5}));
        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.01, 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();

    var globalFontFamily = DEFAULT_FONT_FAMILY;
    var globalFontColor = DEFAULT_FONT_COLOR;
    if (!use_default_fonts)
    {
            var qGlobalFontFamily = form.comboBox({name: "formFontFamily", label: "Global font family", alternatives: font_families, default_value: DEFAULT_FONT_FAMILY, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
            controls.push(qGlobalFontFamily);
            globalFontFamily = qGlobalFontFamily.getValue();
            var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: "Global font color", default_value: DEFAULT_FONT_COLOR});
            controls.push(qGlobalFontColor);
            globalFontColor = qGlobalFontColor.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: true, 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", required: false}));
            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"}));
        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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
            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: 8, 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, required: false}));
        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, 8, "");
    formattedTextControls("Footer", "Footer", controls, 8, "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, increment: 0.5, default_value: (chartType == "Radar" || chartType == "Scatter") ? DEFAULT_GRID_WIDTH : 0});
    controls.push(qXGridWidth);
    if (qXGridWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formCategoriesGridColor", label: "Grid line color", default_value: DEFAULT_GRID_COLOR}));
    var qXLineWidth = form.numericUpDown({name: "formCategoriesLineWidth", label: "Axis line width", minimum: 0, default_value: 0, incrment: 0.5});
    controls.push(qXLineWidth);
    if (qXLineWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formCategoriesLineColor", label: "Axis line color", default_value: DEFAULT_AXIS_COLOR}));
    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, 10, "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, 9, 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: DEFAULT_GRID_WIDTH, increment: 0.5});
    controls.push(qYGridWidth);
    var showYgrid = qYGridWidth.getValue() > 0;
    if (showYgrid)
        controls.push(form.colorPicker({name: "formValuesGridColor", label: "Grid line color", default_value: DEFAULT_GRID_COLOR}));
    var qYLineWidth = form.numericUpDown({name: "formValuesLineWidth", label: "Axis line width", minimum: 0, default_value: 0, increment: 0.5});
    controls.push(qYLineWidth);
    if (qYLineWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formValuesLineColor", label: "Axis line color", default_value: DEFAULT_AXIS_COLOR}));
    var qYZeroLineWidth = form.numericUpDown({name: "formValuesZeroLineWidth", label: "Axis zero line width", minimum: 0, default_value: DEFAULT_AXIS_WIDTH, increment: 0.5, 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: DEFAULT_AXIS_COLOR}));
    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, 10, "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, 9);



    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, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
        controls.push(qHoverFontFamily);
        var qHoverFontSize = form.numericUpDown({name: "formHoverFontSize", label: "Hovertext font size", default_value: 9}); 
        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.5
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)
    }
}
default.grid.color = "#D2D8E1"
default.axis.color = "#1C283B"

# Determine default number of decimals to show
dn <- dimnames(formTable1)
number.type <- dn[[length(dn)]][1]
if (isTRUE(grepl("%", number.type)))
    number.type <- "Percent"
default.digits <- QAppearance$Number$DecimalPlaces[[number.type]]
if (is.null(default.digits))
    default.digits <- QAppearance$Number$DecimalPlaces[["None"]]
.ifNull <- function(a, b) if (is.null(a)) return(b) else return(a)

# Note that using the default proportion test "Non-parametric" sets QSetting$Assumptions to "ChiSquare" 
stat.assumptions <- QSettings$StatisticalAssumptions
if (is.null(stat.assumptions))
    stat.assumptions <- list(ProportionTest = "ChiSquare", ProportionBessel = FALSE, 
    MeansTest = "tTest", MeansBessel = TRUE, DesignEffectConstance = 1)
print(stat.assumptions)

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 = stat.assumptions$ProportionTest,
                  proportions.bessel = stat.assumptions$ProportionBessel,
                  means.test = stat.assumptions$MeansTest,
                  means.bessel = stat.assumptions$MeanBessel,
                  design.effect.constant = stat.assumptions$DesignEffectConstant,
                  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 = .ifNull(get0("formPStatDecimals"), default.digits),
                  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 = .ifNull(get0("formDiffDecimals"), default.digits),
                  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
    if (get0("formHidePercent", ifnotfound=FALSE))
    {
        dlen <- length(dimnames(dat))
        pstat <- dimnames(dat)[[dlen]][1]
        if (grepl("%", pstat))
            dimnames(dat)[[dlen]][1] <- gsub("%", "Percent", pstat)
    }

    # Set defaults if no template
    template <- get0("formTemplate")
    if (is.null(template))
    {
        default.palette <- QSettings$ColorPalette
        default.font <- QAppearance$Font$Family
        default.color <- "#444444"
        template <- list(colors = default.palette, 
            global.font = list(family = default.font, color = default.color,
            size = 8, units = "pt"), fonts = list(`Data labels` = list(
            family = default.font, color = default.color, size = 8), Legend = list(
            family = default.font, color = default.color, size = 8), Title = list(
            family = default.font, color = default.color, size = 12), Subtitle = list(
            family = default.font, color = default.color, size = 8), Footer = list(
            family = default.font, color = default.color, size = 8), `Categories axis title` = list(
            family = default.font, color = default.color, size = 10), `Categories axis tick labels` = list(
            family = default.font, color = default.color, size = 9), `Values axis title` = list(
            family = default.font, color = default.color, size = 10), `Values axis tick labels` = list(
            family = default.font, color = default.color, size = 9), `Panel title` = list(
            family = default.font, color = default.color, size = 10), `Hover text` = list(
            family = default.font, color = default.color, size = 9)))
    }
    data.statistic <- ""
    if (!is.null(attr(dat, "statistic"))) {
        data.statistic <- attr(dat, "statistic")
    } else if (is.array(dat) && !is.null(attr(dat, "questions"))) {
        dn <- dim(dat)
        data.statistic <- dimnames(dat)[[length(dn)]][1]
    }
    data.is.percentage <- grepl("%", data.statistic, fixed = TRUE)
    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"), .ifNull(get0("formHoverDecimals"), default.digits)), data.labels.format.list = list(get0("formDataLabelNumberType"), get0("formDataLabelDateType"), get0("formDataLabelCustom"), get0("formDataLabelSeparateThousands"), .ifNull(get0("formDataLabelDecimals"), default.digits)), as.percentages = data.is.percentage)
   
    # 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 = .ifNull(get0("formDiffDecimals"), default.digits),
        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 = default.axis.color),
        values.zero = sum(get0("formValuesZeroLineWidth")) > 0,
        values.zero.line.width = get0("formValuesZeroLineWidth", ifnotfound = 0),
        values.zero.line.color = get0("formValuesZeroLineColor", ifnotfound = default.axis.color),
        values.grid.width = get0("formValuesGridWidth", ifnotfound = 0),
        values.grid.color = get0("formValuesGridColor", ifnotfound = default.grid.color),
        categories.line.width = get0("formCategoriesLineWidth", ifnotfound = 0),
        categories.line.color = get0("formCategoriesLineColor", ifnotfound = default.axis.color),
        categories.grid.width = get0("formCategoriesGridWidth", ifnotfound = 0),
        categories.grid.color = get0("formCategoriesGridColor", ifnotfound = default.grid.color),
        # 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
viz <- if (formOutput == "Table of Differences") table.of.diffs else chart.with.annot
{
    "formOutput": "Stacked Column Chart with Statistical Significance"
}