Visualization - Area - Area

From Q
(Redirected from Visualization - Area Chart)
Jump to navigation Jump to search
VizIcon Area Chart.svg

An Area Chart is a line chart with filling in the area between the line and axis. They are particularly useful for comparing multiple quantities over time and part-of-whole comparisons.

Examples

The examples below use data from a fast-food consumption survey. The first visualization uses the first table to show the fast-food consumption habits of respondents for one fast-food chain. The visualizations that follow use the multivariate table with data from three chains.

Single variable table Multivariate table
Burger sales 1 chain.png Burger sales 3 chains.png

Single Area Chart

The Single Area Chart is an area chart of a single series. The example below shows the consumption numbers for a fast-food chain over a period of time.

Create a Single Area Chart in Displayr

1. Go to Insert > Visualization > Area Chart
2. Under Inputs > DATA SOURCE > Output in ‘Pages’, select your table from the dropdown menu

Overlapping Area Chart

The Overlapping Area Chart presents values from multiple series overlayed on top of each other. The example below compares the consumption patterns for three fast-food chains over time.

Create an Overlapping Area Chart in Displayr

1. Go to Insert > Visualization > Area Chart
2. Under Inputs > DATA SOURCE > Output in ‘Pages’, select your table from the dropdown menu

Stacked Area Chart

The Stacked Area Chart presents values from multiple series stacked on top of each other, showing both the individual and cumulative values. The example below compares the consumption patterns for three fast-food chains over time, and also shows the cumulative totals.

Create a Stacked Area Chart in Displayr

1. Go to Insert > Visualization > Area Chart
2. Under Inputs > DATA SOURCE > Output in ‘Pages’, select your table from the dropdown menu
3. Under Inputs > OUTPUT, check Stack series

100% Stacked Area Chart

A 100% Stacked Area Chart rescales the values of each series so that they all add up to 100%, presenting the values as a proportion of the whole. The example below presents a part-of-whole comparison of the consumption patterns for three fast-food chains over time.

Create a 100% Stacked Area Chart in Displayr

1. Go to Insert > Visualization > Area Chart
2. Under Inputs > DATA SOURCE > Output in ‘Pages’, select your table from the dropdown menu
3. Under Inputs > OUTPUT, check Stack series
4. Under Inputs > DATA MANIPULATION, check Convert to percentages/proportions

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.

More Information

How to make an area chart in Displayr
How to make an area chart in Excel
How to make an area chart in R


Code

// VERSION 6.1.1
var allow_control_groups = Q.fileFormatVersion() > 10.9; // Group controls for Displayr and later versions of Q
var displayr = Q.isOnTheWeb();
var controls = []; // All controls displayed must be inserted here
if (allow_control_groups)
    form.group("OUTPUT");
// Chart type selector.
// Separate words in chart types e.g. scatter plot not scatterplot
var qChartType = form.comboBox({name: "formChartType", label: "Chart type",
                               visible: displayr ? false : true,
                               alternatives: ["Table", 
                                              "Area", 
                                              "Bar",
                                              "Bar Pictograph",
                                              "Bean", 
                                              "Box",
                                              "Column",
                                              "Density",
                                              "Donut",
                                              "Funnel",  
                                              "Geographic Map",
                                              "Heat", 
                                              "Histogram", 
                                              "Line",
                                              "Palm", 
                                              "Pie",
                                              "Radar", 
                                              "Stream", 
                                              "Scatter",
                                              "Time Series",
                                              "Venn", 
                                              "Violin"], default_value: "Area", required: true});
var chartType = qChartType.getValue();
controls.push(qChartType);
var default_aggregation = ["Bean", "Box", "Density", "Histogram", "Violin", "Scatter", "Time Series", "Table"].indexOf(chartType) == -1

// Some constants and helper functions
function isEmpty(x) { return (x == undefined || x == null || 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 == "") }
function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; }
function rgbToHex(x) { return "#" + componentToHex(x[0]) + componentToHex(x[1]) + componentToHex(x[2]); }

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;

// Default axis names
var categoriesAxisLabel = "CATEGORIES (X) AXIS";
var valuesAxisLabel = "VALUES (Y) AXIS";
function SwappedXY() { categoriesAxisLabel = "CATEGORIES (Y) AXIS"; 
                       valuesAxisLabel = "VALUES (X) AXIS"; }
function SwappedDistXY() { categoriesAxisLabel = "FREQUENCY (Y) AXIS"; 
                           valuesAxisLabel = "VALUES (X) AXIS"; }

// * 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"];
 
// * 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"];

// * 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"];
gradual_palettes = ["Default or template settings", "Blues, light to dark", "Blues, dark to light", "Greys, light to dark", "Greys, dark to light", "Reds, light to dark", "Reds, dark to light", "Greens, light to dark", "Greens, dark to light", "Spectral colors (red, yellow, blue)", "Spectral colors (blue, yellow, red)","Heat colors (yellow, red)", "Terrain colors (green, beige, grey)", "Custom gradient", "Custom palette"];
line_subslice_palletes = ["Group colors"].concat(palettes);
default_colors = ["#3e7dcc", "#04b5ac", "#f5c524", "#c44e41", "#8cc0ff", "#ff905a", "#345e8c", "#04827b", "#967f47", "#96362f", "#2c4374", "#4d525a"];

// *Controls linked to specific chart types*
// - We uses lowercase camel to represent the input data controls. These are not the names of the actual controls.
// - The input data controls for a specific chart type are store in 'input_data'.
// - All other controls that modify the data are stored in 'INPUTS'.
// - Other than for 'INPUTS', the name of an array of controls becomes the name of the group on the Charts tab. 
// - The names used to refer to individual controls in the arrays are the same as the names of the R parameters (where there is a one-to-one match)

// Default controls (modified below for specific charts)
APPEARANCE = null; GRIDLINES = null; MARGINS = null;
DATA_SERIES = ['Colors'];
DATA_LABELS = ['DataLabelShow', 'DataLabelDecimals', 'DataLabelFont', 'DataLabelPrefix', 'DataLabelSuffix'];
TITLE = ['Title', 'Subtitle', 'Footer'];
tidy = true;
xLabel = "Variables";
yLabel = 'Groups';
INPUTS_data = ['pasted', 'table', 'r', 'variables'];
variables_types = ['variables'];
CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesAxisShow', 'CategoriesTickMaxNum',
    'CategoriesTickFont', 'CategoriesTickAngle', 'LabelWrap',
    'CategoriesNumberFormat', 'CategoriesPrefix','CategoriesMin', 'CategoriesMax',
    'CategoriesGrid','CategoriesLine', 'CategoriesTickLen', 'CategoriesTickColor'];
VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickMaxNum', 'ValuesTickFont', 
    'ValuesNumberFormat', 'ValuesMin', 'ValuesMax', 'ValuesTickLen', 'ValuesTickColor',
    'ValuesGrid', 'ValuesLine', 'ValuesZeroLine', 'ValuesPrefix', 'ValuesSuffix']; 
HOVER = ['HoverNumberFormat', 'HoverFontFamily', 'HoverFontSize'];
LEGEND = ['LegendShow', 'LegendFont', 'LegendXPos', 'LegendYPos', 'LegendOrientation', 'LegendWrap'];
INPUTS = ['x', 'y', "AsPercentages", "HidePercentSymbol", "FirstAggregate", "DateFormat", "CategoricalAsBinary"];
FONT = ['GlobalFontFamily', 'GlobalFontColor', 'FontUnits'];
BACKGROUND = ['BgColor', 'BgOpacity'];
ANNOTATIONS = null;
if (chartType == "Venn")
{
    TITLE = null; CATEGORIES_AXIS = null; VALUES_AXIS = null; LEGEND = null;
    DATA_SERIES = ['Colors', 'FillOpacity'];
    FONT = ['FontUnits', 'GlobalFontFamily'];
    INPUTS_data = ['binaryMulti', 'r', 'pasted', 'table', 'variables'];
    variables_types = ['Questions: PickAny', 'variables']; 
    INPUTS = ['AsPercentages', 'x'];
    tidy = false;
    DATA_LABELS = ["DataLabelFont", "DataLabelFontMulticolor"];
    hover_number_formats = ["Automatic", "Number", "Percentage"];
    BACKGROUND = null;
} else if (chartType == "Stream")
{
    categoriesAxisLabel = "X AXIS";
    valuesAxisLabel = "Y AXIS";
    DATA_LABELS = TITLE = LEGEND = null;
    FONT = ['GlobalFontColor', 'GlobalFontFamily', 'GlobalFontSize', 'FontUnits'];
    CATEGORIES_AXIS = ['CategoriesNumberFormat']; // Additional controls are defined below
    VALUES_AXIS = ['ValuesNumberTicks', 'ValuesAxisShow', 'ValuesNumberFormat'];
    categories_number_formats = ["Automatic", "Number", "Date/Time", "Scientific", "Custom"];
    values_number_formats = ["Automatic", "Number", "Scientific", "Custom"];
    HOVER = ['HoverFontFamily', 'HoverFontColor', 'HoverFontSize'];
    MARGINS = ['Margin'];
    BACKGROUND = null;
}else if (chartType == "Pie" || chartType == "Donut")
{
    var radiusLabel = "Donut hole radius %";
    if (chartType == "Pie")
    {
        radiusLabel = "Radius of pie groupings % (multi-column tables only)";
        DATA_SERIES.push('SubsliceColors');
    }
    CATEGORIES_AXIS = null; VALUES_AXIS = null; LEGEND = null;
    HOVER = ['HoverFontFamily', 'HoverFontSize', 'HoverFontColor', 'HoverThreshold', 'HoverBgColor'];
    DATA_LABELS = ["DataLabelFormat", "DataLabelFont", "DataLabelPrefix", "DataLabelSuffix"];
    APPEARANCE = ["PieRadius", "BorderColor"];
    BACKGROUND = null;
 
} else if (chartType == "Radar")
{
    categoriesAxisLabel = "CATEGORIES (ANGULAR) AXIS";
    valuesAxisLabel = "VALUES (RADIAL) AXIS";
    CATEGORIES_AXIS = ['CategoriesAxisShow', 'CategoriesTickFont', 
        'CategoriesTickAngle', 'LabelWrap', 'CategoriesGrid'];
    VALUES_AXIS = ['ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 
        'ValuesMin', 'ValuesMax', 'ValuesGrid'];
    hover_number_formats = ["Automatic", "Number", "Percentage"];
    DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont', 
        'DataLabelFontMulticolor', 'DataLabelPrefix', 'DataLabelSuffix'];
    APPEARANCE = ['SmallMultiples'];
    LEGEND.push('LegendShowAuto');
    TITLE.push("HAlign");
    MARGINS = ['CustomMargin'];
    DATA_SERIES.push('FillOpacity');
    DATA_SERIES.push('LineThickness');
    DATA_SERIES.push('LineMarkers');
    HOVER.push('HoverShow');
    ANNOTATIONS = ["", "Arrow - up", "Arrow - down", "Border", "Caret - up", "Caret - down", 
        "Hide", "Shadow", "Text - after data label", "Text - before data label"];

} else if (chartType == "Palm")
{
    CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesTickFont'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 
        'ValuesSuffix', 'ValuesPrefix'];
    INPUTS = ['x', "AsPercentages", "HidePercentSymbol", "FirstAggregate"];
    values_number_formats = ["Automatic", "Number", "Percentage", "Currency"];
    DATA_LABELS = null;
    LEGEND = ['LegendFont'];
    HOVER = ['HoverShow', 'HoverFontFamily', 'HoverFontSize'];
    BACKGROUND = null;
} else if (chartType == "Time Series")
{
    INPUTS = ['x', 'y'];
    values_number_formats = ["Automatic", "Number", "Percentage", "Currency", 
        "Metric units suffix", "Scientific"];
    hover_number_formats = ["Automatic", "Number", "Percentage", "Currency", 
        "Metric units suffix", "Scientific"];
    DATA_LABELS = null;
    TITLE = ['Title'];
    LEGEND = ['LegendXPos', 'LegendOrientation'];
    HOVER = ['HoverNumberFormat', 'HoverFontFamily', 'HoverFontSize', 'HoverFontColor'];
    CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesTickFont'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesTickFont', 'ValuesMin', 'ValuesMax', 'ValuesNumberFormat', 
        'ValuesSuffix', 'ValuesPrefix'];
    APPEARANCE = ["WindowStart", "RangeBars"];
    DATA_SERIES.push('LineThickness');
    BACKGROUND = null;
} else if (chartType == "Geographic Map")
{
    DATA_LABELS = null;
    TITLE = null;
    LEGEND = ['LegendShow', 'LegendTitle', 'LegendFont'];
    HOVER = ['HoverNumberFormat', 'HoverFontSize', 'HoverFontFamily'];
    hover_number_formats = ["Automatic", "Number", "Percentage"];
    CATEGORIES_AXIS = null;
    VALUES_AXIS = ['ValuesMin', 'ValuesMax'];
    palettes = gradual_palettes;
    APPEARANCE = ["MapPackage", "SmallMultiples"];
    BACKGROUND = null;
} else if (chartType == "Heat")
{
    palettes = gradual_palettes;
    LEGEND = ['LegendShow', 'LegendFont'];
    DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont', 'DataLabelPrefix', 'DataLabelSuffix'];
    categoriesAxisLabel = "X AXIS";
    valuesAxisLabel = "Y AXIS";
    CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesAxisShow', 'CategoriesTickFont'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesMin', 'ValuesMax'];
    HOVER = ['HoverNumberFormat', 'HoverPrefixSuffix', 'HoverFontFamily', 'HoverFontSize'];
    hover_number_formats = ['Automatic', 'Number', 'Percentage', 'Scientific'];
    APPEARANCE = ['SortOrStandardize', 'AdditionalColumns'];
    BACKGROUND = null;
} else if (chartType == "Scatter")
{
    // These settings assume a plotly scatterplot
    // Some changes are made later when isLabeled is determined 
    var isLabeled = false;  // set in DATA MANIPULATION tab
    INPUTS_data = ['pasted', 'table', 'variables'];
    INPUTS = ['x', 'y', 'HidePercentSymbol'];
    DATA_SERIES.push('FillOpacity');
    DATA_SERIES.push('MarkerSize');
    categoriesAxisLabel = "X AXIS";
    valuesAxisLabel = "Y AXIS";
    xLabel = 'X coordinates';
    yLabel = 'Y coordinates';
    variables_types = ['variables','Table','RItem'];
    FITLINE = ['FitLine'];
    HOVER = ['HoverFontFamily', 'HoverFontSize'];
    APPEARANCE = ['SmallMultiples']; 
    LEGEND.push('LegendShowAuto');
    DATA_LABELS = null;
    MARGINS = ['CustomMargin'];

}  else if (["Column", "Bar", "Area", "Line"].indexOf(chartType) != -1)
{
    APPEARANCE = ['SmallMultiples'];    
    if (chartType == "Bar")
         SwappedXY();
    if (chartType != "Line")
        APPEARANCE.push('Stacked');
    if (chartType == "Line")
        DATA_SERIES = ['Colors', 'LineType', 'LineThickness', 'LineShape', 'LineMarkers'];
    if (chartType == "Bar" || chartType == "Column")
        APPEARANCE.push("BarGap");
    DATA_SERIES.push("FillOpacity");
    FITLINE = ['FitLine'];
    MARGINS = ['CustomMargin'];
    APPEARANCE.push("SmallMultiples");
    TITLE.push("HAlign");
    LEGEND.push('LegendShowAuto');
    HOVER.push('HoverShow');
    DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont', 'DataLabelFontMulticolor', 
        'DataLabelPrefix', 'DataLabelSuffix'];
    if (chartType == "Column" || chartType == "Bar")
        ANNOTATIONS = ["", "Arrow - up", "Arrow - down", "Border", "Caret - up", "Caret - down", 
            "Circle - filled", "Circle - thick outline", "Circle - thin outline", "Hide", "Shadow", 
            "Text - after data label", "Text - before data label"];
    if (chartType == "Line")
        ANNOTATIONS = ["", "Arrow - up", "Arrow - down", "Border", "Caret - up", "Caret - down", 
            "Hide", "Shadow", "Text - after data label", "Text - before data label"];

}  else if (["Box", "Bean", "Density", "Histogram", "Violin"]. indexOf(chartType) != -1)
{    
    INPUTS_data = ['pasted', 'table', 'r', 'variables', 'sets'];
    INPUTS = ['x', 'y', 'HidePercentSymbol'];
    variables_types = ['variables'];
    APPEARANCE = ['DensityColor', 'VerticalDistributon'];
    DATA_SERIES = null; DATA_LABELS = null; LEGEND = null;
    categoriesAxisLabel = "FREQUENCY (X) AXIS";
    valuesAxisLabel = "VALUES (Y) AXIS";
    CATEGORIES_AXIS = ['CategoriesTickFont', 'LabelWrap', 'CategoriesTickAngle', 'CategoriesTickLen'];
    vertical = ['Violin', 'Box'].indexOf(chartType) > -1;
    if (chartType == 'Violin')
        APPEARANCE.push('ShowMean', 'Bandwidth', 'AutomaticLower');
    else if (chartType == 'Bean') {
        APPEARANCE.push('ValuesColor', 'Bandwidth', 'AutomaticLower');
        HOVER = ['HoverFontFamily', 'HoverFontSize'];
    } else if (chartType == 'Density') {
        APPEARANCE.push('ValuesColor', 'ShowValues', 'Bandwidth', 'AutomaticLower');
        HOVER = ['HoverFontFamily', 'HoverFontSize'];
    } else if (chartType == 'Box')
        APPEARANCE.push('BoxPoints', 'ValuesColor');
    else if (chartType == 'Histogram')
        APPEARANCE.push('HistogramCumulative', 'HistogramCounts', 'AutomaticBinning', 'MaximumBins');
    else if (chartType != 'Bean')
        APPEARANCE.push('ShowValues');
    TITLE.push("HAlign");
    HOVER.push('HoverShow');
    MARGINS = ['CustomMargin'];

} else if (chartType == "Funnel")
{
    SwappedXY();
    APPEARANCE = ['SmallMultiples', 'BarGap'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesMax'];
    DATA_SERIES.push("FillOpacity");
    TITLE.push("HAlign");
    FITLINE = null;
    LEGEND = null;
    DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont', 'DataLabelFontMulticolor', 
        'DataLabelPrefix', 'DataLabelSuffix'];
    MARGINS = ['CustomMargin'];
    HOVER.push('HoverShow');
    ANNOTATIONS = ["", "Arrow - up", "Arrow - down", "Border", "Caret - up", "Caret - down", 
        "Circle - filled", "Circle - thick outline", "Circle - thin outline", "Hide", "Shadow", 
        "Text - after data label", "Text - before data label"];

} else if (chartType == "Bar Pictograph")
{
    SwappedXY();
    INPUTS = ['x', 'AsPercentages', 'HidePercentSymbol', 'FirstAggregate', 'CategoricalAsBinary']; // only accepts 1-d tables
    CATEGORIES_AXIS = ['CategoriesAxisShow', 'CategoriesTickFont', 'CategoriesTickHorizAlign'];
    DATA_LABELS = ['DataLabelFont', 'DataLabelFormat',
        'DataLabelPrefix', 'DataLabelSuffix', 'DataLabelPosition'];
    APPEARANCE = ['Icon', 'FillDirection', 'IconPadding'];
    VALUES_AXIS = null;
    HOVER = null;
    LEGEND = null;
    TITLE = null;
    BACKGROUND = ['BgColor'];
} else if(chartType == "Table")
{
    INPUTS_data = ['pasted', 'table', 'r', 'variables', 'binaryMulti', 'sets', 'set'];
    DATA_SERIES = DATA_LABELS = TITLE = CATEGORIES_AXIS = null;
    VALUES_AXIS = HOVER = LEGEND = FONT = null;
    BACKGROUND = null;
} else if(chartType != "None")
    throw "Unknown chart type";

var use_default_fonts = false;
var globalFontFamily = DEFAULT_FONT_FAMILY;
var globalFontColor = DEFAULT_FONT_COLOR;
var globalFontSize = 8;

if (chartType == "Table")
{
    var qTableAutoFit = form.checkBox({label: "Autofit", name: "formTableAutoFit", default_value: false});
    controls.push(qTableAutoFit);
}


var show_as_small_mult = false; 
var isStacked = false;
if (APPEARANCE != null)
{
    if (APPEARANCE.indexOf('SmallMultiples') > -1)
    {
        var qSmallMult = form.checkBox({label: "Show as small multiples (panel chart)", name: "formSmallMultiples", visible: displayr ? false : true, default_value: false, prompt: "Show each data series in its own panel."});
        controls.push(qSmallMult);
        show_as_small_mult = qSmallMult.getValue();
    }
    if (!show_as_small_mult && APPEARANCE.indexOf("Stacked") > -1)
    {
        var qStack = form.checkBox({label: "Stack series", name: "formStackSeries", visible: displayr ? false : true, default_value: false, prompt: "Stack bars on top of each other to compare cumulative values."});
        var isStacked = qStack.getValue();
        controls.push(qStack);
    }
}

// Radar chart + Small Multiples ignore margin sizes
// Note that older versions by default have margin.autoexpand on
// But this does nothing useful except add excess space
// So we do not offer it as an option
if (chartType == "Radar" && show_as_small_mult)
    MARGINS = null; 

/* 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,
                                 horizAlign = false)
{
    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);
            }
        }
    }
    if (horizAlign)
        controls.push(form.comboBox({name: "form" + name + "HAlign", label: "Alignment", alternatives: ["Left", "Center", "Right"], default_value: "Center"}));
}

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: "#5C9AD3"}));
            controls.push(form.colorPicker({name: "form" + suffix + "CustomGradientEnd", label: "Gradient end" + labelSuffix, default_value: "#ED7D31"}));
        }
        if (colOpt == "Custom palette")
        {
            controls.push(form.textBox({name: "form" + suffix + "CustomPalette", label: "Custom palette" + labelSuffix, default_value: default_colors.toString(), 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"}));

        if (gradual_palettes.indexOf(colOpt) > 0 && addColorValues)
            controls.push(form.dropBox({name: "form" + suffix + "ColorValues" + suffix, label: "Values", required: false,
                            types: ["RItem:numeric,integer,array", "Table"], prompt: "Optional numeric R output to specify the points from the color scale to be used"}));
}

formatCellControls = function(name, controls, defaultWeight, defaultHAlign, defaultBorderWidth = 1, formatFont = true)
{
    var borderWidth = form.numericUpDown({name: "formTable" + name + "BorderWidth", label: "Border width", default_value: defaultBorderWidth});
    controls.push(borderWidth);
    if (borderWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formTable" + name + "BorderColor", label: "Border color", default_value: "#FFFFFF"}));
    controls.push(form.colorPicker({name: "formTable" + name + "Fill", label: "Fill color",  default_value: "transparent"}));


    if (formatFont)
    {
        controls.push(form.colorPicker({name: "formTable" + name + "FontColor", label: "Font color", default_value: globalFontColor}));
        controls.push(form.comboBox({name: "formTable" + name + "FontFamily", 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(form.numericUpDown({name: "formTable" + name + "FontSize", label: "Font size", increment: 0.5, default_value: globalFontSize}));
        controls.push(form.comboBox({name: "formTable" + name + "FontWeight", label: "Font weight", alternatives: ["Normal", "Bold"], default_value: defaultWeight}));
        controls.push(form.comboBox({name: "formTable" + name + "FontStyle", label: "Font style", alternatives: ["Normal", "Italic"], default_value: "Normal"}));
        var qHorizAlign = form.comboBox({name: "formTable" + name + "AlignHoriz", label: "Horizontal alignment", alternatives: ["Left", "Center", "Right"], default_value: defaultHAlign});
        controls.push(qHorizAlign);
        if (qHorizAlign.getValue() != "Center")
            controls.push(form.numericUpDown({name: "formTable" + name + "Pad", label: "Padding", default_value: 0, prompt: "Space between cell border and text in pixels"}));
        controls.push(form.comboBox({name: "formTable" + name + "AlignVertical", label: "Vertical alignment", alternatives: ["Top", "Middle", "Bottom"], default_value: "Middle"}));
    }
}

annotControls = function(controls, annotTypes, cell_stats, useDataVar = false)
{
    var aii = 1;
    var atype = "";
    while (aii == 1 || atype != "")
    {
       var astr = " (" + aii + ")"
       var annotType = form.comboBox({name: "formAnnotType" + aii, label: "Annotation" + astr, alternatives: annotTypes, default_value: "", prompt: "Type of annotation to add to data labels. Multiple annotations can be added. They will be applied to the data labels consecutively. Note that 'Hide', 'Border' and 'Shadow' only have a visible effect if there is already text in the annotation."});
        controls.push(annotType);
        atype = annotType.getValue();
        if (atype != "")
        {
            var isTextAnnot = atype == "Text - after data label" || atype == "Text - before data label";
            var useDataVarI = useDataVar;
            if (useDataVarI)
            {  
                var annotDataOpt = form.checkBox({label: "Add data from table or variable", name: "formAnnotDataMethod" + aii, default_value: true, prompt: "Unselect if the data for this annotation is already in the input data. That is, it is (1) a statistic in the Y coordinate table; or (2) it is a variable or column in the input data; or (3) it has already been added for another annotation. In that case, select the annotation data by typing the name of the statistic or column into the 'Data" + astr + "' textbox."});
                controls.push(annotDataOpt);
                useDataVarI = annotDataOpt.getValue();

                if (useDataVarI)
                    controls.push(form.dropBox({label: "Data" + astr, name: "formAnnotDataVar" + aii, types: variables_types, prompt: "Select variable to control annotation", multi: false, required: true}));
            }
            
            if (!useDataVarI)
            {
                // Use a textbox if user has constructed an R table
                if (cell_stats == undefined || cell_stats == false)
                    controls.push(form.textBox({name: "formAnnotData" + aii, label: "Data (cell statistic)" + astr, required: false, prompt: "Name of statistic in the Data Source to be compared against threshold (e.g. 'p' or 'Base n'). Leave blank to use chart data"}));
                else
                    controls.push(form.comboBox({name: "formAnnotData" + aii, label: "Data (cell statistic)" + astr, required: true, alternatives: cell_stats, prompt: "Name of statistic in the Data Source to be compared against threshold (e.g. 'p' or 'Base n')"}));
            }
            controls.push(form.comboBox({name: "formAnnotThresType" + aii, label: "Show annotations for values", alternatives: ["above threshold", "below threshold", "which are missing"], default_value: "above threshold"}));
            controls.push(form.textBox({name: "formAnnotThreshold" + aii, label: "Threshold" + astr, required: true, default_value: isTextAnnot ? " " : "-Inf", prompt: "Enter a numeric value (e.g. '0.05') to condition on numeric annotation data. If the annotation data is text, enter text value for string comparison."}));
            if (atype != "Hide")
            {
                controls.push(form.colorPicker({name: "formAnnotColor" + aii, label: "Color" + astr, default_value: "#CD343C"}));
                if (atype != "Border" && atype != "Marker border")
                    controls.push(form.numericUpDown({name: "formAnnotSize" + aii, label: "Size" + astr, default_value: 15}));
            }
            if (atype == "Border" || atype == "Marker border")
                controls.push(form.numericUpDown({name: "formAnnotWidth" + aii, label: "Line width" + astr, default_value: 2}));
            if (atype == "Border")
                controls.push(form.numericUpDown({name: "formAnnotOffset" + aii, label: "Offset" + astr, default_value: 0}));
            if (["Text - before data label", "Text - after data label"].indexOf(atype) != -1)
            {
                controls.push(form.textBox({name: "formAnnotFormat" + aii, label: "Format" + astr, required: false, prompt: "D3 format e.g. '.2f', '.0%'"}));
                controls.push(form.textBox({name: "formAnnotPrefix" + aii, label: "Prefix" + astr, required: false, prompt: "Optional text to prepend to text annotation"}));
                controls.push(form.textBox({name: "formAnnotSuffix" + aii, label: "Suffix" + astr, required: false, prompt: "Optional text to append to text annotation"}));
                controls.push(form.comboBox({name: "formAnnotFontFamily" + aii, label: "Font family" + astr, 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.comboBox({name: "formAnnotFontWeight" + aii, label: "Font weight" + astr, alternatives: ["normal", "bold"], default_value: "normal"}));
                controls.push(form.comboBox({name: "formAnnotFontStyle" + aii, label: "Font style" + astr, alternatives: ["normal", "italic"], default_value: "normal"}));
            }
            if (atype.match(/^Text|^Arrow|^Border|^Shadow/))
            {
                controls.push(form.numericUpDown({name: "formAnnotShiftRight" + aii, label: "Shift right" + astr, default_value: 0}));
            }
            if (atype.match(/^Circle/))
            {
                controls.push(form.numericUpDown({name: "formAnnotShiftLeft" + aii, label: "Shift left" + astr, default_value: 0}));
                controls.push(form.numericUpDown({name: "formAnnotShiftRight" + aii, label: "Shift right" + astr, default_value: 0}));
            }
        }
        aii++;
    }
}

annotOverlayControls = function(controls, cell_stats, chartType, autoColor = false)
{
    var ind = 1;
    var defaultType = "";
    var annotType = defaultType;
    var annotTypes = [defaultType, "Arrow - up", "Arrow - down", "Caret - up", "Caret - down", "Text", "Custom text"];
    while (ind == 1 || annotType != defaultType)
    {
       var astr = " (" + ind + ")"
       form.group("Annotation" + astr);
       var annotType = form.comboBox({name: "formAnnotOverlayType" + ind, label: "Type", alternatives: annotTypes, default_value: defaultType});
        controls.push(annotType);
        annotType = annotType.getValue();
        if (annotType != defaultType)
        {
            var isTextAnnot = annotType == "Text";
            if (annotType == "Custom text")
                controls.push(form.textBox({name: "formAnnotOverlayCustomSymbol" + ind, label: "Custom text", required: false}));

            if (cell_stats == undefined)
                controls.push(form.textBox({name: "formAnnotOverlayData" + ind, label: "Data", required: true, prompt: "Name of statistic in the Data Source to be compared against threshold (e.g. 'p' or 'Base n')"}));
            else
                controls.push(form.comboBox({name: "formAnnotOverlayData" + ind, label: "Data", required: true, alternatives: cell_stats, prompt: "Name of statistic in the Data Source to be compared against threshold (e.g. 'p' or 'Base n')"}));

            controls.push(form.comboBox({name: "formAnnotOverlayThresType" + ind, label: "Show annotations for values", alternatives: ["above threshold", "below threshold"], default_value: "above threshold"}));
            controls.push(form.textBox({name: "formAnnotOverlayThreshold" + ind, label: "Threshold", required: true, default_value: isTextAnnot ? " " : "-Inf", prompt: "Enter a numeric value (e.g. '0.05') to condition on numeric annotation data. If the annotation data is text, enter text value for string comparison."}));
            
            if (chartType != "Radar")
            {
                controls.push(form.numericUpDown({name: "formAnnotOverlayRelativePos" + ind, label: "Position relative to value", required: true, default_value: 0.0, minimum: 0.0, maximum: 1.0, increment: 0.01, prompt: "Position of annotation relative to bar, ranging from 0.0 (base of bar) to 1.0 (top of bar)"}));
                controls.push(form.comboBox({name: "formAnnotOverlayHAlign" + ind, label: "Horizontal alignment", required: true, alternatives: ["Left", "Center", "Right"], default_value: "Center"}));
                controls.push(form.comboBox({name: "formAnnotOverlayVAlign" + ind, label: "Vertical alignment", required: true, alternatives: ["Top", "Middle", "Bottom"], default_value: "Top"}));
                controls.push(form.numericUpDown({name: "formAnnotOverlayOffset" + ind, label: "Offset", default_value: 5, prompt: "Number of pixels to move the annotation. The direction depends on the alignment."}));
            }
            controls.push(form.numericUpDown({name: "formAnnotOverlaySize" + ind, label: "Size", default_value: 15}));
            controls.push(form.comboBox({name: "formAnnotOverlayFontFamily" + ind, label: "Font family", default_value: globalFontFamily, alternatives: font_families, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
            if (autoColor)
            {
                var autoColCtrl = form.checkBox({name: "formAnnotOverlayAutoColor" + ind, label: "Automatically color annotation according to series", default_value: true});
                controls.push(autoColCtrl);
            }
            if (!autoColor || !autoColCtrl.getValue())
                controls.push(form.colorPicker({name: "formAnnotOverlayColor" + ind, label: "Color", default_value: "#CD343C"}));
            if (isTextAnnot)
            {
                controls.push(form.textBox({name: "formAnnotOverlayFormat" + ind, label: "Format", required: false, prompt: "D3 format e.g. '.2f', '.0%'"}));
                controls.push(form.textBox({name: "formAnnotOverlayPrefix" + ind, label: "Prefix", required: false, prompt: "Optional text to prepend to text annotation"}));
                controls.push(form.textBox({name: "formAnnotOverlaySuffix" + ind, label: "Suffix", required: false, prompt: "Optional text to append to text annotation"}));
            }
        }
        ind++;
    }
}
 
/* Creating the controls */
// Creating the data inputs.
if (allow_control_groups)
    form.group("DATA SOURCE");

// Input as linked Q or R table(s)
var outputLabel = "Output";
var outputPrompt = "Outputs are tables or other Q or R outputs";
if (displayr)
{
   outputLabel = "Data";
   outputPrompt = "Data is tables or other outputs in your Data Sources (bottom-left) or Report tree (top-left)";
}

var multiTableInput = chartType == "Scatter" && !show_as_small_mult;
var tableInput = form.dropBox({name: "formTable", label: outputLabel, types: ['Table', "RItem:!StandardChart!SignificanceTest:!KMeans:!phraseList:!tidyText:!wordBag"], required: false, multi: multiTableInput, prompt: outputPrompt});

// Input as variables in the Data tab
var variables_controls = []; 
var variables_prompt = displayr ? "Variables are found in Data Sources (bottom-left)" :
       "Variables are shown in the 'Variables and Questions' tab.";
if (INPUTS.indexOf('x') > -1) 
{
    var varX = form.dropBox({label: xLabel, name: "formX", types: variables_types, prompt: variables_prompt, multi: chartType != "Scatter", required: false});
    var n_variables = 1;
    if (allow_control_groups && chartType != "Scatter")
        n_variables = varX.getValues().length;
    variables_controls.push(varX);
    if (chartType == "Scatter" && Q.fileFormatVersion() > 17.05 && !isEmpty(varX))
        variables_controls.push(form.checkBox({label: "Use category labels instead of values", name: "formXSpan", default_value: false, prompt: "Position markers on the x-axis by the span or row labels instead of the values in the table. This option will be ignored for variables"}));

}
var n_yvariables = 0;
if (INPUTS.indexOf('y')> -1 && (chartType == "Scatter" || (n_variables >= 1)))
{
    var varY = form.dropBox({label: yLabel, name: "formY", types: variables_types, prompt: variables_prompt, multi: chartType == "Scatter", required: false});
    variables_controls.push(varY);
    if (!isEmpty(varY))
        n_yvariables = 1;
    if (allow_control_groups && chartType == "Scatter")
        n_yvariables = varY.getValues().length; 
}

if (chartType == "Scatter" && n_yvariables <= 1)
{
    var VLabels = form.dropBox({label: "Labels",name: "formScatterLabels", types:['variables'], prompt: variables_prompt, multi: false, required: false});
    variables_controls.push(VLabels);
    
    var VSizes = form.dropBox({label: "Sizes", name: "formZ", types: variables_types, prompt: variables_prompt, multi: false, required: false});
    variables_controls.push(VSizes);
    if (!isEmpty(VSizes) && Q.fileFormatVersion() > 17.05)
        variables_controls.push(form.checkBox({label: "Use category labels instead of values", name: "formVSizesSpan", default_value: false, prompt: "Marker sizes set according to the span or row labels instead of the values in the table. This option will be ignored for variables"}));


    var VColors = form.dropBox({label: "Colors", name: "formZ2", types: variables_types, prompt: variables_prompt, multi: false, required: false});
    variables_controls.push(VColors);
    if (!isEmpty(VColors) && Q.fileFormatVersion() > 17.05)
        variables_controls.push(form.checkBox({label: "Use category labels instead of values", name: "formVColorsSpan", default_value: false, prompt: "Markers colored according to the span or row labels instead of the values in the table. This option will be ignored for variables"}));

    if (show_as_small_mult)
    {
        var VGroups = form.dropBox({label: "Groups", name: "formGroups", types: variables_types, prompt: "The group variable is used to assign data points to a particular grid in the small multiple", multi: false, required: !isEmpty(varX) || !isEmpty(varY), default_value: VColors.getValue()})
        variables_controls.push(VGroups);
        if (!isEmpty(VGroups) && Q.fileFormatVersion() > 17.05)
            variables_controls.push(form.checkBox({label: "Use category labels instead of values", name: "formVGroupsSpan", default_value: false, prompt: "Markers assigned to panels according to the span or row labels instead of the values in the table. This option will be ignored for variables"}));
    }
}

// Pasted input data
var pastedInput = form.dataEntry({label: "Paste or type data", name: "formPastedData", prompt: "Opens a spreadsheet into which you can enter data.", required: true, large_data_error: "The data entered is too large. The best alternative is to add your data as a Data Set, use Table > Raw Data > Variable(s), and connect that table to this analysis."});

// Hide other data sources if one of them is already selected
var tableInputObj = null;
if (!allow_control_groups || !isEmpty(tableInput) ||
    (isBlankSheet(pastedInput) && isEmpty(varX) && isEmpty(varY)))
     controls.push(tableInput);
if (!allow_control_groups || (!isEmpty(varX) || !isEmpty(varY)) || 
    (isEmpty(tableInput) && isBlankSheet(pastedInput)))
    controls = controls.concat(variables_controls);
if (!allow_control_groups || !isBlankSheet(pastedInput) ||
    (isEmpty(tableInput) && isEmpty(varX) && isEmpty(varY))) 
     controls.push(pastedInput);


// Figure out type of data input to customize data processing options
var dtype = "";
if (!allow_control_groups || !isEmpty(tableInput))
{
    dtype = 'table';
    if (allow_control_groups && chartType == "Scatter" && tableInput.getValues() != null && tableInput.getValues().length > 1)
    {
        dtype = 'tables';
        isLabeled = true;
    }
}
else if (!allow_control_groups || !isEmpty(varX) || !isEmpty(varY))
    dtype = 'variables';
else if (!isBlankSheet(pastedInput))
    dtype = 'pasted';

// Remove CategoricalAsBinary and other INPUTS options not appropriate for table inputs 
if (dtype != "variables")
{
    if (chartType == "Heat")
        INPUTS = ['x', 'y', "FirstAggregate"]; // not Venn
    // Should Palm trees use 'Row %' similar to column charts?
}

if (yLabel == "Groups" && allow_control_groups && !isEmpty(varY))
{
    var zi = INPUTS.indexOf('FirstAggregate');
    if (zi > -1)
        INPUTS.splice(zi, 1);
}

if (!allow_control_groups || dtype != "")
{
    if (allow_control_groups)
        form.group("DATA MANIPULATION");
    var scatter_mult_ys = false;
    if (chartType == "Scatter" && dtype != 'variables' && dtype != 'tables')
    {
        var qScatterMultYs = form.checkBox({label: "Input data contains y-values in multiple columns", name: "formScatterMultYvals", default_value: false, prompt: "Select checkbox if data table is in long format rather than a wide format."});
        scatter_mult_ys = qScatterMultYs.getValue();
        controls.push(qScatterMultYs);
    }
    var aggregate = false;
    if (INPUTS.indexOf("FirstAggregate") > -1)
    {
        var qAggregate = form.checkBox({label: "Aggregate the data prior to plotting", name: "formFirstAggregate", default_value: dtype == 'variables' && default_aggregation, prompt: "The data is 'raw', where each row represents an individual case. It needs to be aggregated prior to plotting."});
        controls.push(qAggregate);
        aggregate = qAggregate.getValue();
    }
    if (tidy && ANNOTATIONS == null)
    {
        var qTidy = form.checkBox({label: "Automatically tidy the data", name: "formTidy", default_value: ["Table", "Bar", "Column", "Line", "Radar", "Scatter"].indexOf(chartType) == -1, prompt: "Convert to numeric and simplify structure. Leave unselected to use multi-statistic table with annotations."});
        tidy = qTidy.getValue();
        controls.push(qTidy);
    }
    if (aggregate && allow_control_groups && (dtype == 'table' || dtype == 'pasted'))
         controls.push(form.checkBox({label: "Group by last column (crosstab)", name: "formGroupByLastColumn", default_value: true, prompt: "If more than two columns are provided, automatically groups by the last column"}));
    if (aggregate && (!allow_control_groups || (dtype == 'variables' && n_variables == 2 && n_yvariables == 0)))
    { 
         controls.push(form.checkBox({label: "Group by last variable (crosstab)", name: "formGroupByLastColumn", default_value: false, prompt: "If two variable are provided, automatically groups by the last variable"}));
    }
    if (dtype == 'pasted')
    {
        var qAutoName = form.checkBox({label: "Automatically detect row and column names", name: "formNotDataFrame", default_value: true, prompt: "Unselect to manually adjust structure of table"});
        var notDF = qAutoName.getValue();
        controls.push(qAutoName);
        if (!notDF)
        {
            var rowname_label = "First column contains row names";
            var rowname_prompt = "Values will be shown as labels in the categories axis";
            var rowname_default = false;
            if (chartType == "Scatter")
            {
                if (!scatter_mult_ys)
                {
                    rowname_label = "First column contains data labels";
                    rowname_prompt = "Values will be shown as data labels for each point (not X-axis labels)";
                }
                rowname_default = true;
            }
            controls.push(form.checkBox({label: "First row contains column names", name: "formPastedColumnNames", default_value: true}));
            controls.push(form.checkBox({label: rowname_label, name: "formPastedRowNames", prompt: rowname_prompt, default_value: rowname_default}));
        }
    }
    var labelExtracted = "common prefixes";
    if (chartType == "Scatter")
        labelExtracted = "common prefixes and row span labels";
    qTidyLabel = form.checkBox({name: "formTidyLabels", label: "Tidy labels", default_value: true, prompt: "Extract " + labelExtracted + " to simplify labels"});
    controls.push(qTidyLabel);
    if (chartType != 'Donut' && chartType != "Scatter")
    {
        qTranspose = form.checkBox({name: "formTranspose", label: "Switch rows and columns", prompt: "Read each row of the input table as a data series"});
        controls.push(qTranspose);
    }

    // Creating other input controls on the Inputs page.
    var asPct = false;
    if (INPUTS != null)
    {
        if (INPUTS.indexOf('HidePercentSymbol') > -1)
            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}));

        if (INPUTS.indexOf('AsPercentages') > -1)
        {
            var qAsPct = form.checkBox({name: "formAsPercentages", label: "Convert to percentages/proportions", prompt: "Compute percentages based on data in input tables (after row and column manipulations are applied)."});
            asPct = qAsPct.getValue();
            controls.push(qAsPct);
        }
        if (INPUTS.indexOf('DateFormat') > -1)
        {
            var qDate = form.comboBox({name: "formDateFormat", label: "Date format", alternatives: ['Automatic', 'International (dd/mm/yyyy)', 'US (mm/dd/yyyy)', 'No date formatting'], default_value: 'Automatic', prompt: "Control how date strings in input table is read"});
            controls.push(qDate);
        }
        if (dtype == 'variables')
        {
            var qVarNames = form.checkBox({label: "Variable names", name:"formNames", default_value: false, prompt: "Show variable names instead of variable labels"});
            controls.push(qVarNames);
        }
        if (INPUTS.indexOf("CategoricalAsBinary") > -1 && n_variables > 1)
        {
            var qCatAsBin = form.checkBox({label: "Categorical as binary", name: "formCategoricalAsBinary", default_value: n_yvariables > 0});
            controls.push(qCatAsBin);
        }

        var qHideOutput = form.checkBox({label: "Hide output with small sample sizes", name: "formHideOutput", default_value: false, prompt: "Show error if any cell of the table has 'Column n' or 'Base n' smaller than the cut-off"});
        controls.push(qHideOutput);
        if (qHideOutput.getValue())
        {
            var qHideOutputThres = form.textBox({label: "Sample size cut-off", name: "formHideOutputThres", default_value: "30", type: "number", required: true});
            controls.push(qHideOutputThres);
        }
        var qHideValues = form.checkBox({label: "Hide values with small sample sizes", name: "formHideValues", default_value: false, prompt: "Show values as missing values if cell has 'Column n' or 'Base n' smaller than the cut-off"});
        controls.push(qHideValues);
        if (qHideValues.getValue())
        {
            var qHideValuesThres = form.textBox({label: "Sample size cut-off", name: "formHideValuesThres", default_value: "30", type: "number", required: true});
            controls.push(qHideValuesThres);
        }
        if (allow_control_groups) 
            form.group('Row manipulations');
        var qHideEmptyRows = form.checkBox({label: "Hide empty rows", name: "formHideEmptyRows", default_value: false, prompt: "Hide rows containing only missing values."});

        controls.push(qHideEmptyRows);
        var qHideRowsBySize = form.checkBox({label: "Hide rows with small sample sizes", name: "formHideRowsBySize", default_value: false, prompt: "Remove rows with 'Base n' smaller than the sample size cut-off"});
        controls.push(qHideRowsBySize);
        if (qHideRowsBySize.getValue())
        {
            var qHideRowsThres = form.textBox({label: "Sample size cut-off", name: "formHideRowsThres", default_value: "30", type: "number", required: true});
            controls.push(qHideRowsThres);
        }
        var qSelectRowsOpt;
        if (displayr)
        {
            // Do not offer this option in Q, especially old versions (< 5.6) of Q
            var rowOpts = ["Typing row names or indices", "Choosing from Combo Box or List Box control", "Calculation output"]
            qSelectRowsOpt = form.comboBox({label: "Select rows to show by", name: "formSelectRowsOpt", alternatives: rowOpts, default_value: rowOpts[0]});
            controls.push(qSelectRowsOpt);
        }

        if (!!qSelectRowsOpt && qSelectRowsOpt.getValue() == rowOpts[1])
            var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl", types: ["Control"], required: false, calculations: true});
        else
            var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl", types: ["RItem:character"], required: false});
        var qSelectRowsText = form.textBox({label: "Rows to show", name: "formSelectRows", type: "text", required: false, prompt: "Enter comma separated list of row indices or names"});

        if (!!qSelectRowsOpt && qSelectRowsOpt.getValue() != rowOpts[0])
            controls.push(qSelectRowsCtrl);
        else
            controls.push(qSelectRowsText);

        var qSortRows = form.checkBox({label: "Sort rows", name: "formSortRows", default_value: false, prompt: "Sort rows by the values in a specified column"});
        controls.push(qSortRows);
        if (qSortRows.getValue())
        {
            var qSortRowsColumn = form.textBox({label: "Column used for sorting rows", name: "formSortRowsColumn", type: "text", required: false, prompt: "Enter column index or name. Last column used if none specified"});
            controls.push(qSortRowsColumn);
            var qSortRowsDecr = form.checkBox({label: "Sort in decreasing order", name: "formSortRowsDecr", default_value: false});
            controls.push(qSortRowsDecr);

            var qSortRowsExclude = form.textBox({label: "Rows to exclude from sorting", name: "formSortRowsExclude", type: "text", required: false, default_value: "NET, Total, SUM", prompt: "Enter comma separated list of row indices or names"});
            controls.push(qSortRowsExclude);
        }
        if (!qSortRows.getValue())
        {
            var qAutoOrderRows = form.checkBox({label: "Order rows by similarity", name: "formAutoOrderRows", default_value: false, prompt: "Use correspondence analysis and order rows by coordinates in first dimension"});
            controls.push(qAutoOrderRows)
        }
        var qReverseRows = form.checkBox({label: "Reverse rows", name: "formReverseRows", default_value: false});
        controls.push(qReverseRows);
        var qRowsIgnore = 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(qRowsIgnore);       
 
 var qFirstKRows = form.textBox({label: "Number of rows from top to show", name: "formFirstKRows", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qFirstKRows);
        var qLastKRows = form.textBox({label: "Number of rows from bottom to show", name: "formLastKRows", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qLastKRows);

        controls.push(form.textBox({name: "formRowLabels", label: "Row labels", default_value: "", required: false, prompt: "Specify row names as a comma separated list to override default labels from the input data (optional). This occurs after other data manipulation steps have been completed"}));

        if (allow_control_groups)
            form.group('Column manipulations');
        var qHideEmptyCols = form.checkBox({label: "Hide empty columns", name: "formHideEmptyCols", default_value: false, prompt: "Hide columns containing only missing values"});
        controls.push(qHideEmptyCols);
        var qHideColsBySize = form.checkBox({label: "Hide columns with small sample sizes", name: "formHideColsBySize", default_value: false, prompt: "Remove columns with 'Base n' or Column n' smaller than the sample size cut-off"});
        controls.push(qHideColsBySize);
        if (qHideColsBySize.getValue())
        {
            var qHideColsThres = form.textBox({label: "Sample size cut-off", name: "formHideColsThres", default_value: "30", type: "number", required: true});
            controls.push(qHideColsThres);
        }
        var qSelectColsOpt;
        if (displayr)
        {
            // Do not offer this option in Q, especially old versions (< 5.6) of Q
            var colOpts = ["Typing column names or indices", "Choosing from Combo Box or List Box control", "Calculation output"]
            qSelectColsOpt = form.comboBox({label: "Select columns to show by", name: "formSelectColsOpt", alternatives: colOpts, default_value: colOpts[0]});
            controls.push(qSelectColsOpt);
        }

        if (!!qSelectColsOpt && qSelectColsOpt.getValue() == colOpts[1])
            var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl", types: ["Control"], required: false, calculations: true});
        else    
            var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl", types: ["RItem:character"], required: false});
        var qSelectColsText = form.textBox({label: "Columns to show", name: "formSelectCols",
					    type: "text", required: false,
					    prompt: "Enter comma separated list of column indices or names"});
        if (!!qSelectColsOpt && qSelectColsOpt.getValue() != colOpts[0])
            controls.push(qSelectColsCtrl);
        else
            controls.push(qSelectColsText);
        var qSortCols = form.checkBox({label: "Sort columns", name: "formSortCols", default_value: false,
				       prompt: "Sort columns by the values in a specified row"});
        controls.push(qSortCols);
        if (qSortCols.getValue())
        {
            var qSortColsRow = form.textBox({label: "Row used for sorting columns", name: "formSortColsRow", type: "text", required: false, prompt: "Enter row index or name. Last row used if none specified"});
            controls.push(qSortColsRow);
            var qSortColsDecr = form.checkBox({label: "Sort in decreasing order", name: "formSortColsDecr", default_value: false});
            controls.push(qSortColsDecr);

            var qSortColsExclude = form.textBox({label: "Columns to exclude from sorting", name: "formSortColsExclude", type: "text", required: false, default_value: "NET, Total, SUM", prompt: "Enter comma separated list of column indices or names"});
            controls.push(qSortColsExclude);
        }
        if (!qSortCols.getValue())
        {
            var qAutoOrderCols = form.checkBox({label: "Order columns by similarity", name: "formAutoOrderCols", default_value: false, prompt: "Use correspondence analysis and order columns by coordinates in first dimension"});
            controls.push(qAutoOrderCols)
        }
        var qReverseCols = form.checkBox({label: "Reverse columns", name: "formReverseCols", default_value: false});
        controls.push(qReverseCols);
        var qColsIgnore = form.textBox({label: "Columns to ignore", type: "text", default_value: "NET, Total, SUM", name: "formIgnoreCols", required: false, prompt: "Specify columns to hide as a comma seperated list of column names"});
        controls.push(qColsIgnore);

        var qFirstKCols = form.textBox({label: "Number of columns from left to show", name: "formFirstKCols", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qFirstKCols);
        var qLastKCols = form.textBox({label: "Number of columns from right to show", name: "formLastKCols", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qLastKCols);

        controls.push(form.textBox({name: "formColumnLabels", label: "Column labels", default_value: "", required: false, prompt: "Specify column names as a comma separated list to override default labels from the input data (optional). This occurs after other data manipulation steps have been completed"}));
    }
}

var show_second_yaxis = false;
if (chartType == "Column" && !show_as_small_mult)
{
    if (allow_control_groups)
        form.group("DATA SOURCE - Second Y Axis");

    // Input as an R Output or Q table
    var tableInput2 = form.dropBox({name: "formTable2", label: outputLabel, types: ['Table', "RItem:!StandardChart!SignificanceTest:!KMeans:!phraseList:!tidyText:!wordBag"], required: false, multi: false, prompt: outputPrompt});

    // Input as variables in the Data tab
    var variables_controls2 = []; 
    var varX2 = form.dropBox({label: xLabel, name: "formX2", types: variables_types, prompt: variables_prompt, multi: true, required: false});
    var n_variables = 1;
    if (allow_control_groups)
        n_variables = varX.getValues().length;
    variables_controls2.push(varX2);
    var n_yvariables = 0;
    if (n_variables >= 1)
    {
        var varY2 = form.dropBox({label: "Groups", name: "formY2", types: variables_types, prompt: variables_prompt, multi: false, required: false});
        variables_controls2.push(varY2);
        if (!isEmpty(varY2))
            n_yvariables = 1;
    }

    // Pasted input data
    var pastedInput2 = form.dataEntry({label: "Paste or type data", name: "formPastedData2", prompt: "Opens a spreadsheet into which you can enter data.", required: false, large_data_error: "The data entered is too large. The best alternative is to add your data as a Data Set, use Table > Raw Data > Variable(s), and connect that table to this analysis."});

    // Hide other data sources if one of them is already selected
    if (!allow_control_groups || !isEmpty(tableInput2) ||
        (isBlankSheet(pastedInput2) && isEmpty(varX2) && isEmpty(varY2)))
         controls.push(tableInput2);
    if (!allow_control_groups || (!isEmpty(varX2) || !isEmpty(varY2)) || 
        (isEmpty(tableInput2) && isBlankSheet(pastedInput2)))
        controls = controls.concat(variables_controls2);
    if (!allow_control_groups || !isBlankSheet(pastedInput2) ||
        (isEmpty(tableInput2) && isEmpty(varX2) && isEmpty(varY2))) 
         controls.push(pastedInput2);

    if (!allow_control_groups || !isBlankSheet(pastedInput2) || 
        !isEmpty(tableInput2) || !isEmpty(varX2))
        show_second_yaxis = true;
}

if (show_second_yaxis)
{
    var dtype2 = "";
    if (!allow_control_groups || !isEmpty(tableInput2))
        dtype2 = 'table';
    else if (!allow_control_groups || !isEmpty(varX2) || !isEmpty(varY2))
        dtype2 = 'variables';
    else if (!isBlankSheet(pastedInput2))
        dtype2 = 'pasted';

    /*if (yLabel == "Groups" && allow_control_groups && !isEmpty(varY2))
    {
        var zi = INPUTS.indexOf('FirstAggregate');
        if (zi > -1)
            INPUTS.splice(zi, 1);
    }*/

    if (allow_control_groups)
        form.group("DATA MANIPULATION - Second Y axis");
    var aggregate = false;
    if (true) //INPUTS.indexOf("FirstAggregate") > -1)
    {
        var qAggregate = form.checkBox({label: "Aggregate the data prior to plotting", name: "formFirstAggregate2", default_value: dtype2 == 'variables', prompt: "The data is 'raw', where each row represents an individual case. It needs to be aggregated prior to plotting."});
        controls.push(qAggregate);
        aggregate = qAggregate.getValue();
    }
    var tidy2 = tidy;
    if (tidy2)
    {
        var qTidy2 = form.checkBox({label: "Automatically tidy the data", name: "formTidy2", default_value: chartType != "Table" && chartType != "Scatter", prompt: "Convert to numeric and simplify structure."});
        tidy2 = qTidy2.getValue();
        controls.push(qTidy2);
    }
    if (aggregate && allow_control_groups && (dtype2 == 'table' || dtype2 == 'pasted'))
         controls.push(form.checkBox({label: "Group by last column (crosstab)", name: "formGroupByLastColumn2", default_value: true, prompt: "If more than two columns are provided, automatically groups by the last column"}));
    if (aggregate && (!allow_control_groups || (dtype2 == 'variables' && n_variables == 2 && n_yvariables == 0)))
         controls.push(form.checkBox({label: "Group by last variable (crosstab)", name: "formGroupByLastColumn2", default_value: false, prompt: "If two variable are provided, automatically groups by the last variable"}));
    if (dtype2 == 'pasted')
    {
        var qAutoName = form.checkBox({label: "Automatically detect row and column names", name: "formNotDataFrame2", default_value: true, prompt: "Unselect to manually adjust structure of table"});
        var notDF = qAutoName.getValue();
        controls.push(qAutoName);
        if (!notDF)
        {
            var rowname_label = "First column contains row names";
            var rowname_prompt = "Values will be shown as labels in the categories axis";
            var rowname_default = false;
            if (chartType == "Scatter")
            {
                if (!scatter_mult_ys)
                {
                    rowname_label = "First column contains data labels";
                    rowname_prompt = "Values will be shown as data labels for each point (not X-axis labels)";
                }
                rowname_default = true;
            }
            controls.push(form.checkBox({label: "First row contains column names", name: "formPastedColumnNames2", default_value: true}));
            controls.push(form.checkBox({label: rowname_label, name: "formPastedRowNames2", prompt: rowname_prompt, default_value: rowname_default}));
        }
    }
    qTidyLabel = form.checkBox({name: "formTidyLabels2", label: "Tidy labels", default_value: chart_type_default != "Table", prompt: "Extract common prefixes to simplify labels"});
    controls.push(qTidyLabel);
    if (chartType != 'Donut' && chartType != "Scatter")
        controls.push(form.checkBox({name: "formTranspose2", label: "Switch rows and columns", prompt: "Read each row of the input table as a data series"}));

    // Creating other input controls on the Inputs page.
    var asPct = false;
    if (INPUTS != null)
    {
        controls.push(form.checkBox({name: "formHidePercent2", label: "Hide percent symbol", prompt: "Percentage data will be treated as if it was just numeric data", default_value: false}));
        if (INPUTS.indexOf('AsPercentages') > -1)
        {
            var qAsPct = form.checkBox({name: "formAsPercentages2", label: "Convert to percentages/proportions", prompt: "Compute percentages based on data in input tables (after row and column manipulations are applied)."});
            asPct = qAsPct.getValue();
            controls.push(qAsPct);
        }
        if (INPUTS.indexOf('DateFormat') > -1)
        {
            var qDate = form.comboBox({name: "formDateFormat2", label: "Date format", alternatives: ['Automatic', 'International (dd/mm/yyyy)', 'US (mm/dd/yyyy)', 'No date formatting'], default_value: 'Automatic', prompt: "Control how date strings in input table is read"});
            controls.push(qDate);
        }
        if (dtype == 'variables')
        {
            var qVarNames = form.checkBox({label: "Variable names", name:"formNames2", default_value: false, prompt: "Show variable names instead of variable labels"});
            controls.push(qVarNames);
        }
        if (INPUTS.indexOf("CategoricalAsBinary") > -1 && n_variables > 1)
        {
            var qCatAsBin = form.checkBox({label: "Categorical as binary", name: "formCategoricalAsBinary2", default_value: n_yvariables > 0});
            controls.push(qCatAsBin);
        }

        var qHideOutput = form.checkBox({label: "Hide output with small sample sizes", name: "formHideOutput2", default_value: false, prompt: "Show error if any cell of the table has 'Base n' smaller than the cut-off"});
        controls.push(qHideOutput);
        if (qHideOutput.getValue())
        {
            var qHideOutputThres = form.textBox({label: "Sample size cut-off", name: "formHideOutputThres2", default_value: "30", type: "number", required: true});
            controls.push(qHideOutputThres);
        }

        if (allow_control_groups) 
            form.group('Row manipulations - Second Y Axis');
        var qHideEmptyRows = form.checkBox({label: "Hide empty rows", name: "formHideEmptyRows2", default_value: false, prompt: "Hide rows containing only missing values."});

        controls.push(qHideEmptyRows);
        var qHideRowsBySize = form.checkBox({label: "Hide rows with small sample sizes", name: "formHideRowsBySize2", default_value: false, prompt: "Remove rows with 'Base n' smaller than the sample size cut-off"});
        controls.push(qHideRowsBySize);
        if (qHideRowsBySize.getValue())
        {
            var qHideRowsThres = form.textBox({label: "Sample size cut-off", name: "formHideRowsThres2", default_value: "30", type: "number", required: true});
            controls.push(qHideRowsThres);
        }
        if (displayr)
        {
            // Do not offer this option in Q, especially old versions (< 5.6) of Q
            var rowOpts = ["Typing row names or indices", "Choosing from Combo Box or List Box control", "Calculation output"]
            var qSelectRowsOpt = form.comboBox({label: "Select rows to show by", name: "formSelectRowsOpt2", alternatives: rowOpts, default_value: rowOpts[0]});
            controls.push(qSelectRowsOpt);
        }

        if (!!qSelectRowsOpt && qSelectRowsOpt.getValue() == rowOpts[1])
            var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl2", types: ["Control"], required: false, calculations: true});
        else
            var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl2", types: ["RItem:character"], required: false});
        var qSelectRows = form.textBox({label: "Rows to show", name: "formSelectRows2", type: "text", required: false, prompt: "Enter comma separated list of row indices or names"});

        if (!isEmpty(qSelectRowsCtrl) ||
            (displayr && qSelectRowsOpt.getValue() != rowOpts[0]))
            controls.push(qSelectRowsCtrl);
        else
            controls.push(qSelectRows);
        

        var qSortRows = form.checkBox({label: "Sort rows", name: "formSortRows2", default_value: false, prompt: "Sort rows by the values in a specified column"});
        controls.push(qSortRows);
        if (qSortRows.getValue())
        {
            var qSortRowsColumn = form.textBox({label: "Column used for sorting rows", name: "formSortRowsColumn2", type: "text", required: false, prompt: "Enter column index or name. Last column used if none specified"});
            controls.push(qSortRowsColumn);
            var qSortRowsDecr = form.checkBox({label: "Sort in decreasing order", name: "formSortRowsDecr2", default_value: false});
            controls.push(qSortRowsDecr);

            var qSortRowsExclude = form.textBox({label: "Rows to exclude from sorting", name: "formSortRowsExclude2", type: "text", required: false, default_value: "NET, Total, SUM", prompt: "Enter comma separated list of row indices or names"});
            controls.push(qSortRowsExclude);
        }
        if (!qSortRows.getValue())
        {
            var qAutoOrderRows = form.checkBox({label: "Order rows by similarity", name: "formAutoOrderRows2", default_value: false, prompt: "Use correspondence analysis and order rows by coordinates in first dimension"});
            controls.push(qAutoOrderRows)
        }
        var qReverseRows = form.checkBox({label: "Reverse rows", name: "formReverseRows2", default_value: false});
        controls.push(qReverseRows);
        var qRowsIgnore = form.textBox({label: "Rows to ignore", type: "text", default_value: "NET, Total, SUM", name: "formIgnoreRows2", required: false, prompt: "Specify rows to hide as a comma seperated list of row names"});
        controls.push(qRowsIgnore);

        var qFirstKRows = form.textBox({label: "Number of rows from top to show", name: "formFirstKRows2", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qFirstKRows);
        var qLastKRows = form.textBox({label: "Number of rows from bottom to show", name: "formLastKRows2", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qLastKRows);

        controls.push(form.textBox({name: "formRowLabels2", label: "Row labels", default_value: "", required: false, prompt: "Specify row names as a comma separated list to override default labels from the input data (optional). This occurs after other data manipulation steps have been completed"}));

        if (allow_control_groups)
            form.group('Column manipulations - Second Y Axis');
        var qHideEmptyCols = form.checkBox({label: "Hide empty columns", name: "formHideEmptyCols2", default_value: false, prompt: "Hide columns containing only missing values"});
        controls.push(qHideEmptyCols);
        var qHideColsBySize = form.checkBox({label: "Hide columns with small sample sizes", name: "formHideColsBySize2", default_value: false, prompt: "Remove columns with 'Base n' or Column n' smaller than the sample size cut-off"});
        controls.push(qHideColsBySize);
        if (qHideColsBySize.getValue())
        {
            var qHideColsThres = form.textBox({label: "Sample size cut-off", name: "formHideColsThres2", default_value: "30", type: "number", required: true});
            controls.push(qHideColsThres);
        }
        if (displayr)
        {
            // Do not offer this option in Q, especially old versions (< 5.6) of Q
            var colOpts = ["Typing row names or indices", "Choosing from Combo Box or List Box control", "Calculation output"]
            var qSelectColsOpt = form.comboBox({label: "Select columns to show by", name: "formSelectColsOpt2", alternatives: colOpts, default_value: colOpts[0]});
            controls.push(qSelectColsOpt);
        }

        if (!!qSelectColsOpt && qSelectColsOpt.getValue() == colOpts[1])
            var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl2", types: ["Control"], required: false, calculations: true});
        else
            var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl2", types: ["RItem:character"], required: false});
         var qSelectCols = form.textBox({label: "Columns to show", name: "formSelectCols2", type: "text", required: false, prompt: "Enter comma separated list of column indices or names"});

        if (!isEmpty(qSelectColsCtrl) ||
            (displayr && qSelectColsOpt.getValue() != colOpts[0]))
            controls.push(qSelectColsCtrl);
        else
           controls.push(qSelectCols);
        

        var qSortCols = form.checkBox({label: "Sort columns", name: "formSortCols2", default_value: false, prompt: "Sort columns by the values in a specified row"});
        controls.push(qSortCols);
        if (qSortCols.getValue())
        {
            var qSortColsRow = form.textBox({label: "Row used for sorting columns", name: "formSortColsRow2", type: "text", required: false, prompt: "Enter row index or name. Last row used if none specified"});
            controls.push(qSortColsRow);
            var qSortColsDecr = form.checkBox({label: "Sort in decreasing order", name: "formSortColsDecr2", default_value: false});
            controls.push(qSortColsDecr);

            var qSortColsExclude = form.textBox({label: "Columns to exclude from sorting", name: "formSortColsExclude2", type: "text", required: false, default_value: "NET, Total, SUM", prompt: "Enter comma separated list of column indices or names"});
            controls.push(qSortColsExclude);
        }
        if (!qSortCols.getValue())
        {
            var qAutoOrderCols = form.checkBox({label: "Order columns by similarity", name: "formAutoOrderCols2", default_value: false, prompt: "Use correspondence analysis and order columns by coordinates in first dimension"});
            controls.push(qAutoOrderCols)
        }
        var qReverseCols = form.checkBox({label: "Reverse columns", name: "formReverseCols2", default_value: false});
        controls.push(qReverseCols);
        var qColsIgnore = form.textBox({label: "Columns to ignore", type: "text", default_value: "NET, Total, SUM", name: "formIgnoreCols2", required: false, prompt: "Specify columns to hide as a comma seperated list of column names"});
        controls.push(qColsIgnore);

        var qFirstKCols = form.textBox({label: "Number of columns from left to show", name: "formFirstKCols2", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qFirstKCols);
        var qLastKCols = form.textBox({label: "Number of columns from right to show", name: "formLastKCols2", type: "number", required: false, prompt: "Enter a number or leave blank"});
        controls.push(qLastKCols);

        controls.push(form.textBox({name: "formColumnLabels2", label: "Column labels", default_value: "", required: false, prompt: "Specify column names as a comma separated list to override default labels from the input data (optional). This occurs after other data manipulation steps have been completed"}));

    }
}


if (chartType == "Table")
{
    if (qTableAutoFit.getValue())
    {
        if (allow_control_groups)
            form.page('Format');
        if (allow_control_groups)
            form.group('Cell values');
        controls.push(form.checkBox({name: "formTableTranspose", label: "Swap rows and columns", default_value: false}));
        var qTableFormat = form.comboBox({name: "formTableFormatType", label: "Number type", default_value: "Automatic",
            alternatives: ['Automatic', 'Percentage', 'Number']});
        controls.push(qTableFormat);
        if (qTableFormat.getValue() == "Percentage")
            controls.push(form.checkBox({name: "formTablePercentSign", label: "Show percentage sign", default_value: true}));
        controls.push(form.numericUpDown({name: "formTableDecimals", label: "Decimal places", required: false}));

        if (allow_control_groups)
            form.group('Font');
        var qGlobalFontFamily = form.comboBox({name: "formFont", 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: !allow_control_groups ? "" : "Global font color", default_value: DEFAULT_FONT_COLOR});
        controls.push(qGlobalFontColor);
        globalFontColor = qGlobalFontColor.getValue();
    
        var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 8, 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);

        if (allow_control_groups)
            form.group('Column header');
        controls.push(form.textBox({name: "formTableColWidths", label: "Column widths", required: false, prompt: "Comma separated values, e.g. '40px, 25%' or leave blank for equal widths"}));
        var qShowColHead = form.checkBox({name: "formColShowHead", label: "Show column headers", default_value: true});
        controls.push(qShowColHead)
        if (qShowColHead.getValue())
        {
           controls.push(form.textBox({name: "formTableColHeadLabels", label: "Column labels", required: false, prompt: "Leave blank to use column names from input table, or give comma separated list"}));
            controls.push(form.textBox({name: "formTableColHeadHeight", label: "Column header row height", required: false, default_value: "35px"}));
            formatCellControls("ColHead", controls, "Bold", "Center");
        }
        if (allow_control_groups)
            form.group('Row header');
        var qShowRowHead = form.checkBox({name: "formRowShowHead", label: "Show row headers", default_value: true});
        controls.push(qShowRowHead)
        if (qShowRowHead.getValue())
        {
            controls.push(form.textBox({name: "formTableRowLabels", label: "Row labels", required: false, prompt: "Leave blank to use row names from input table, or give comma separated list"}));
            formatCellControls("RowHead", controls, "Bold", "Left");
        }

        if (qShowColHead.getValue() && qShowRowHead.getValue())
        {
            if (allow_control_groups)
                form.group('Corner');
            var qCorner = form.textBox({name: "formTableCornerLabels", label: "Corner labels", required: false, prompt: "Text to show in corner cell"});
            controls.push(qCorner);
            formatCellControls("Corner", controls, "Normal", "Center", 0, formatFont = !isEmptyString(qCorner.getValue()));
        }
        if (allow_control_groups)
            form.group('Cells');
        formatCellControls("Cell", controls, "Normal", "Center");
        controls.push(form.textBox({name: "formTableCellPrefix", label: "Prefix", default_value: "", required: false, prompt: "Optional text to prepend to cell content"}));
        controls.push(form.textBox({name: "formTableCellSuffix", label: "Suffix", default_value: "", required: false, prompt: "Optional text to append to cell content"}));
    }
}
else
{
    // Creating controls on the Chart page (forced to be on Inputs tab for Q <= v5.2.1)
    if (allow_control_groups)
        form.page('Chart');
    if (allow_control_groups)
        form.group({label:'APPEARANCE', expanded: true});
    if (ANNOTATIONS != null)
    {
        var qSigTests = form.comboBox({name:"formSigTests", label: "Show significance", alternatives: ["No", "Font colors", "Arrows", "Arrows and font colors", "Carets", "Carets and font colors"], default_value: "No", prompt: "Show significance tests from the input table. Colors of the arrows or carets are set via the Statistical Assumptions settings of the document."});
        controls.push(qSigTests);
        if (qSigTests.getValue() != "No")
            controls.push(form.numericUpDown({name: "formSigTestsSize", label: "Significance arrow size", default_value: 12}));
    }
    var qTemplate = form.dropBox({name: "formTemplate", label: "Use template", types: ["RItem:AppearanceTemplate"], required: false, prompt: template_prompt, calculations: false});
    controls.push(qTemplate);
    use_default_fonts = !isEmpty(qTemplate);
}

if (APPEARANCE != null)
{
    if (APPEARANCE.indexOf("MapPackage") > -1)
    {
        //MapPackage is checked first because we need to determine if smallmultiples should be shown
        var packageValue = "plotly";
        if (!show_as_small_mult)
        {
            var qPackage = form.comboBox({name: "formMapPackage", label: "Map package", alternatives: ["plotly", "leaflet"], default_value: "plotly", prompt: "Choose package used to create maps. Plotly is faster for global and US maps but leaflet allows mapping by zip codes and region-specific maps"});
            controls.push(qPackage);
            packageValue = qPackage.getValue();
        }
        if (packageValue == "plotly")
            TITLE = ['Title', 'Subtitle', 'Footer'];
        if (packageValue == "leaflet")
            HOVER = ['HoverNumberFormat'];
        
        var qHighRes = form.checkBox({label: "High resolution", name: "formHighRes", default_value: false, prompt: "Control level of detail for plotly maps and leaflet world maps"});
        controls.push(qHighRes);
        var qNAasZero = form.checkBox({label: "Treat NA as zero", name: "formNAasZero", default_value: false, prompt: "Should missing values be treated as zero value or colored as a separated category?"});
        controls.push(qNAasZero);
        if (!allow_control_groups)
        {
            var qNAColorLabel = form.newLabel("Color of NA values");
            controls.push(qNAColorLabel);
        }
        var qNAColor = form.colorPicker({name: "formNAColor", label: !allow_control_groups ? "" : "Color of NA values", default_value: "#808080"});
        controls.push(qNAColor);

        var backgroundMapValue = false;
        if (packageValue == "leaflet")
        {
            DATA_SERIES.push("FillOpacity");
            var qShowMissingRegions = form.checkBox({label: "Show missing regions", name: "formShowMissingRegions", default_value: false, prompt: "If unselected, the range of the map will only cover non-missing values"});
            controls.push(qShowMissingRegions);
            var qBackgroundMap = form.checkBox({label: "Background map", name: "formBackgroundMap", default_value: false, prompt: "Use a contextual background map"});
            backgroundMapValue = qBackgroundMap.getValue();
            controls.push(qBackgroundMap);
        }
        
        if (!backgroundMapValue)
            controls.push(form.colorPicker({name: "formOceanColor", label: !allow_control_groups ? "" : "Ocean color", default_value: "#FFFFFF", prompt: "The color of the ocean or background"}));
        
        if (packageValue == "leaflet")        
            controls.push(form.comboBox({name: "formZipCountry", label: "Zip code country", alternatives: ["Automatic", "USA", "Australia", "UK"], default_value: "Automatic", required: true, prompt: "Only used if zip codes are provided and there is ambiguity in the zip codes"}));
    }

    if (APPEARANCE.indexOf('SmallMultiples') > -1 && dtype != 'tables')
    {
        if (qSmallMult.getValue())
        {
            var qSmallMultNRow = form.numericUpDown({label: "Number of rows of panels", name: "formSmallMultNRows", default_value: 2, minimum: 1, prompt: "Control layout of small multiples"});
            controls.push(qSmallMultNRow);
            if (chartType == "Scatter")
                LEGEND = ['LegendShow', 'LegendFont']; 
            else if (chartType != "Geographic Map")
                LEGEND = null; 
            if (chartType != "Geographic Map" && chartType != "Scatter" && chartType != "Funnel")
            {
                var qSmallMultAverage = form.checkBox({label: "Show average of all series", name: "formSmallMultAverage", default_value: false, prompt: "In each panel, show average of combined data for comparison"});
                controls.push(qSmallMultAverage);
                if (qSmallMultAverage.getValue())
                {
                    var qSmallMultAverageColor = form.colorPicker({name: "formSmallMultAverageColor", label: !allow_control_groups ? "" : "Color of average series", default_value: "#999999"});
                    controls.push(qSmallMultAverageColor);
                }
            }
            if (chartType != "Radar" && chartType != "Geographic Map")
            {
                var qSmallMultShareAxes = form.checkBox({label: "Share axes between panels", name: "formSmallMultShareAxes", default_value: true, prompt: "Hold range of axes constant and only show axes on outer panels"});
                controls.push(qSmallMultShareAxes);
            }
            controls.push(form.textBox({label: "Order of panels", name: "formSmallMultXOrder", required: false, prompt: "e.g '2,1,3', or leave blank to follow order of input data"}));
            controls.push(form.numericUpDown({label: "Left padding", name: "formSmallMultPadLeft", default_value: 0.01, minimum: 0.00, maximum: chartType == "Radar" ? 100 : 1.00, increment: 0.01}));
            controls.push(form.numericUpDown({label: "Right padding", name: "formSmallMultPadRight", default_value: 0.01, minimum: 0.00, maximum: chartType == "Radar"? 100 : 1.00, increment: 0.01}));
            controls.push(form.numericUpDown({label: "Top padding", name: "formSmallMultPadTop", default_value: 0.1, minimum: 0.00, maximum: 1.00, increment: 0.01}));
            controls.push(form.numericUpDown({label: "Bottom padding", name: "formSmallMultPadBottom", default_value: 0.01, minimum: 0.00, maximum: 1.00, increment: 0.01}));
        }
    }
    if (chartType == 'Scatter')
    {
        // Scatter is always labeled in INPUTS_types['tables']
        // Never labeled if small multiples chosen
        // For other input types we show dropbox for user to select
        if (!qSmallMult.getValue() && (dtype != 'tables'))
        {
            var knownLabel = dtype == 'variables' && !isEmpty(VLabels);
            var qLabType = form.comboBox({name: "formScatterLabelType", label: "Show labels", alternatives:['As hover text', 'On chart'], default_value: knownLabel ? 'On chart' : 'As hover text'});
            var labtype = qLabType.getValue()
            controls.push(qLabType);
            isLabeled = labtype == 'On chart';
        }
        if (isLabeled)
        {
            VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 
                'ValuesNumberFormat', 'ValuesSuffix', 'ValuesPrefix', 'ValuesMin', 'ValuesMax'];
            CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesAxisShow', 
                'CategoriesTickFont', 'CategoriesNumberFormat', 'LabelWrap',
                'CategoriesPrefix', 'CategoriesMin', 'CategoriesMax'];
            APPEARANCE.push('ScatterLogos');
            APPEARANCE.push('ScatterTrendLine');
            data_label_formats = ["Automatic", "Number", "Category", "Percentage", "Currency"];
            LEGEND = ['LegendShowAuto', 'LegendShow', 'LegendFont', 'LegendBubbles'];
            DATA_LABELS = ['DataLabelFont', 'DataLabelFontMulticolor', 'DataLabelAutoPosition',
                'ScatterMaxLab'];
            GRIDLINES = ['ShowGrid'];
            FITLINE = null;
            BACKGROUND = null;
        }
        if (!isLabeled && ((dtype != 'tables' && dtype != 'variables') || 
            (dtype == 'variables' &&  n_yvariables == 1 && !(allow_control_groups && VSizes.getValue() == null))))
            APPEARANCE.push('ScatterSizeType');
        if ((dtype != 'tables' && dtype != 'variables') || 
            (dtype == 'variables' && n_yvariables == 1 && !(allow_control_groups && VColors.getValue() == null)))
            APPEARANCE.push('ScatterColorType');
    }
    if (APPEARANCE.indexOf("ScatterLogos") > -1)
    {
        controls.push(form.textBox({name: "formLogos", label: "Logos", prompt: "Enter URLs as a comma separated list", type: "Text", required: false}));
        controls.push(form.numericUpDown({name: "formLogoSize", label: "Logo size", default_value: 0.5, increment: 0.1}));
    }
    if (APPEARANCE.indexOf("ScatterTrendLine") > -1)
    {
        var prompt_text = "Add lines between corresponding points in successive tables";
        if (dtype != 'tables')
            prompt_text = "Add lines between points in the same group";
        var scatterTrendLines = form.checkBox({label: "Show trend lines", name: "formTrendLines", default_value: false, prompt: prompt_text});
        controls.push(scatterTrendLines);
        if (scatterTrendLines.getValue())
            LEGEND = null;
    }
    if (APPEARANCE.indexOf("ScatterSizeType") > -1)
    {
        controls.push(form.comboBox({label: "Treat sizes variable as", name:"formScatterSizeType", alternatives:['Area', 'Diameter'], default_value:'Area'}));
    }
    if (!show_as_small_mult && APPEARANCE.indexOf("ScatterColorType") > -1)
    {
        controls.push(form.comboBox({label: "Treat colors variable as", name:"formScatterColorType", alternatives:['Categories', 'Numeric scale'], default_value:'Categories', prompt: "Treat values in the color variables as separate categories (may be slow if there are many categories); or as values on a linear scale"}));
    }


    if (APPEARANCE.indexOf("Icon") > -1)
    {
        var custom_icon_label = "(Custom icon)";
        var qPictoIcon = form.comboBox({name: "formIcon", label: "Icon", alternatives: [custom_icon_label, "Apple","Baby", "Beer", "Bread","Cake", "Car", "Chicken", "Circle", "Cross", "Elephant", "Globe", "Gun", "Heart", "House", "Money", "Soldier", "Square", "Star", "Sick person", "Stick man", "Stick woman", "Thumbs up", "Thumbs down", "Tick", "Trolley", "Truck",  "User", "Water drop", "Weight", "Wine"], default_value: "Stick man"});
        var picto_icon = qPictoIcon.getValue();
        controls.push(qPictoIcon);
        if (picto_icon == custom_icon_label)
        {
            controls.push(form.textBox({name: "formCustomIcon", label: "Icon URL", type: "text", required: true, prompt: "Enter url to icon"}));
            var qCustomBase = form.textBox({name: "formBaseImage", label: "Base icon URL", type: "text", required: false, prompt: "Leave blank to hide unfilled icons"});
            var customBase = qCustomBase.getValue();
            controls.push(qCustomBase);
        }
        else
        {
            var qBaseOpt = form.checkBox({name: "formHideBase", label: "Hide unfilled icons", default_value: true});
            var baseOpt = !qBaseOpt.getValue();
            controls.push(qBaseOpt);
        }
        controls.push(form.textBox({name: "formTotalIcons", label: "Total icons per bar", type: "number", required: false, prompt: "Number of filled and unfilled icons. Leave blank to determine from Input data"}));
        controls.push(form.textBox({name: "formIconScale", label: "Units per icon (scale)", type: "number", required: false, prompt: "Leave blank to determine based on range of Input data"}));
        var qIconNcol = form.textBox({name:"formIconNCol", label: "Maximum icons per row",  type: "number", prompt: "Leave blank for no wrapping (all icons on one line)", required: false});
        controls.push(qIconNcol);
        var ncolOpt = qIconNcol.getValue()
        controls.push(form.comboBox({name:"formFillDirection", label: "Direction of fill", alternatives: ["From left", "From right", "Radial"], default_value: "From left"}));
        if (picto_icon != custom_icon_label)
        {
            if (baseOpt)
                controls.push(form.colorPicker({name: "formBaseColor", label: !allow_control_groups ? "" : "Icon base color", default_value: "#CCCCCC", prompt: "Color of unfilled icons"}));
            var qLabColAsIcon = form.checkBox({name: "formLabelColorAsIcon", label: "Labels colored as icons"});
            controls.push(qLabColAsIcon);
            var labColAsIcon = qLabColAsIcon.getValue();
            if (ncolOpt != "" && !baseOpt)
                controls.push(form.checkBox({name:"formFixNRows", label:"Fixed number of rows per bar", default_value: true}));
        }
        if (picto_icon == custom_icon_label && ncolOpt != "" && !customBase)
        {
            controls.push(form.checkBox({name:"formFixNRows", label:"Fixed number of rows per bar", default_value: true}));
        }
        controls.push(form.numericUpDown({name: "formIconPadding", label: "Space between bars",  minimum: 0, default_value: 2,  increment:1}));
    }

    if (APPEARANCE.indexOf("PieRadius") > -1)
    {
        controls.push(form.numericUpDown({name: "formPieRadius", label: radiusLabel, default_value: 70, increment: 1, minimum: 0, maximum: 100}));
    }
    if (APPEARANCE.indexOf("BorderColor") > -1)
    {
        controls.push(form.colorPicker({name: "formBorderColor", label: allow_control_groups ? "Border color" : "", default_value: "#ffffff"}));
    }

    if (APPEARANCE.indexOf("DensityColor") > -1)
    {
        controls.push(form.colorPicker({name: "formDensityColor", label: allow_control_groups ? "Color" : "", default_value: "#5C9AD3"}));
    }
    if (APPEARANCE.indexOf("VerticalDistributon") > -1)
    {
        var qVertDist = form.checkBox({name: "formVertical", label: "Plot vertically", default_value: vertical, prompt: "Rotate plot by 90 degrees"});
        if (!qVertDist.getValue())
            SwappedDistXY();
        controls.push(qVertDist);
    }
    var show_values_color = true;
    if (APPEARANCE.indexOf("ShowValues") > -1)
    {
        var qShowValues = form.checkBox({name: "formShowValues", label: "Plot the individual data values", default_value: false});
        var show_values_color = qShowValues.getValue();
        controls.push(qShowValues);
    }
    if (show_values_color && APPEARANCE.indexOf("ValuesColor") > - 1)
    {
        var qValuesColor = form.colorPicker({name: "formValuesColor", label: allow_control_groups ? "Data value color" : "", default_value: "#999999"});
        controls.push(qValuesColor);
    }
    if (APPEARANCE.indexOf("ShowMean") > -1)
    {
        var qMean = form.checkBox({name: "formShowMean", label: "Plot the mean value", default_value: true});
        controls.push(qMean);
        if (qMean.getValue())
        {
            if (!allow_control_groups)
            {
                var qMeanColorLabel = form.newLabel("Color of the mean dot");
                controls.push(qMeanColorLabel);
            }
            var qMeanColor = form.colorPicker({name: "formMeanColor", label: !allow_control_groups ? "" : "Color of the mean dot", default_value: "#808080"});
            controls.push(qMeanColor);
        }
        var qMedian = form.checkBox({name: "formShowMedian", label: "Plot the median", default_value: true});
        controls.push(qMedian);
        if (qMedian.getValue())
        {
            if (!allow_control_groups)
            {
                var qMedianColorLabel = form.newLabel("Color of the median line");
                controls.push(qMedianColorLabel);
            }
            var qMedianColor = form.colorPicker({name: "formMedianColor", label: allow_control_groups ? "Color of the median line" : "", default_value: "#000000"});
            controls.push(qMedianColor);
        }
        var qQuartiles = form.checkBox({name: "formShowQuartiles", label: "Plot the quartiles", default_value: true});
        controls.push(qQuartiles);
        if (qQuartiles.getValue())
        {
            if (!allow_control_groups)
            {
                var qQuartileColorLabel = form.newLabel("Color of the quartiles box");
                controls.push(qQuartileColorLabel);
            }
            var qQuartileColor = form.colorPicker({name: "formQuartilesColor", label: allow_control_groups ? "Color of the quartiles box" : "", default_value: "#000000"});
            controls.push(qQuartileColor);
        }
        var qRange = form.checkBox({name: "formShowRange", label: "Plot the range", default_value: true});
        controls.push(qRange);
        if (qRange.getValue())
        {
            if (!allow_control_groups)
            {
                var qRangeColorLabel = form.newLabel("Color of the range line");
                controls.push(qRangeColorLabel);
            }
            var qRangeColor = form.colorPicker({name: "formRangeColor", label: allow_control_groups ? "Color of the range line" : "", default_value: "#000000"});
            controls.push(qRangeColor);
        }
    }
    if (APPEARANCE.indexOf("BoxPoints") > -1)
    {
        var qBoxPoint = form.comboBox({name: "formBoxPoints", label: "Box points", alternatives: ["All", "Outliers", "Suspected outliers"], default_value: "Suspected outliers", required: true, prompt: "Control whether only outliers are shown or all points"});
        controls.push(qBoxPoint);
    }
    if (APPEARANCE.indexOf('HistogramCounts') > -1)
    { 
        var qHistCum = form.checkBox({name: "formHistogramCumulative", label: "Cumulative histogram", default_value: false, prompt: "Show number of observations with values less than or equal to current bin"});
        controls.push(qHistCum);
        var qHistCount = form.checkBox({name: "formHistogramCounts", label: "Show counts", default_value: false});
        controls.push(qHistCount);
        var qHistAutoBin = form.checkBox({name: "formAutomaticBinning", label: "Automatic column widths (bins)", default_value: true});
        controls.push(qHistAutoBin);
        if (!qHistAutoBin.getValue())
        {
            var qHistMaxBin = form.numericUpDown({name: "formMaximumBins", label: "Maximum columns (bins)", default_value: 20});
            controls.push(qHistMaxBin);
        }
    }
    if (APPEARANCE.indexOf('Bandwidth') > -1)
    {
        var qBandwidth = form.numericUpDown({name: "formBandwidth", label: "Bandwidth", minimum: 0.01, maximum: 1, increment: 0.01, default_value: 1, prompt: "The relative bandwidth used in the estimating the density."});
        
        controls.push(qBandwidth);
    }
    if (APPEARANCE.indexOf('AutomaticLower') > -1)
    {
        var qLowerBd = form.checkBox({name: "formAutomaticLower", label: "Automatically compute lower bound", default_value: true});
        controls.push(qLowerBd);
    }
    if (APPEARANCE.indexOf("RangeBars") > -1)
    {
        var qRangeBar = form.checkBox({name: "formRangeBars", label: "Show range bars", default_value: false, prompt: "Data should consist of three columns. The columns with the largest and smallest averages will be used to form the bounds of the shaded region around the other column."});
        controls.push(qRangeBar);
        var qWinStart = form.numericUpDown({name: "formWindowStart", label: "Window start (days from data end)", maximum: 100000, prompt: "By default the full range of the data will be shown. Click up/down arrows to adjust the range"});
        controls.push(qWinStart);
    }
    if (APPEARANCE.indexOf("SortOrStandardize") > -1)
    {
        var qSortRows = form.comboBox({label: "Row sorting or dendrogram", alternatives: ["None", "Sort by averages (ascending)", "Sort by averages (descending)", "Dendrogram"], name: "formHeatSortRows", default_value: "None"});
        controls.push(qSortRows);
        var qSortCols = form.comboBox({label: "Column sorting or dendrogram", alternatives: ["None", "Sort by averages (ascending)", "Sort by averages (descending)", "Dendrogram"], name: "formHeatSortColumns", default_value: "None"});
        controls.push(qSortCols);
        var qStandardize = form.comboBox({label: "Shading standardization", name: "formHeatStandardization", default_value: "None", alternatives: ["None", "Standardize rows", "Standardize columns"]});
        controls.push(qStandardize);
    }
    if (APPEARANCE.indexOf("AdditionalColumns") > -1)
    {
        var qLeftCol = form.dropBox({label: "Left columns", types:["RItem:matrix,data.frame,table,array,integer,numeric,character", "Table"], name: "formLeftColumns",  multi: true, required: false, prompt: "A list of vectors or tables to append to the left of the heat map. Matched by rownames if available."});
        controls.push(qLeftCol);
        var qLeftHead = form.textBox({label: "Left column headings", type: "text", default_value: "", name: "formLeftColumnHeadings", required: false, prompt: "Enter comma seperated list as headings to left columns. Leave blank to use column names of left columns."});
        controls.push(qLeftHead);
        var qRightCol = form.dropBox({label: "Right columns", types:["RItem:matrix,data.frame,table,array,integer,numeric,character", "Table"], name: "formRightColumns",  multi: true, required: false, prompt: "A list of vectors or tables to append to the right of the heat map. Matched by rownames if available."});
        controls.push(qRightCol);
        var qRightHead = form.textBox({label: "Right column headings", type: "text", default_value: "", name: "formRightColumnHeadings", required: false, prompt: "Enter comma seperated list as headings to right columns. Leave blank to use column names of right columns"});
        controls.push(qRightHead);
    }
    if (APPEARANCE.indexOf("BarGap") > -1)
    {
        controls.push(form.numericUpDown({name: "formBarGap", label: "Gap between bars", default_value: 0.33, minimum: 0.0, maximum: 1.0, increment: 0.01, prompt: "Specify spacing between bars at different category axis locations, as a proportion of the width of a single bar."}));
        if (["Bar", "Column"].indexOf(chartType) > -1 && !isStacked && !show_as_small_mult)
            controls.push(form.numericUpDown({name: "formBarGroupGap", label: "Gap between grouped bars", default_value: 0.10, minimum: 0.0, maximum: 1.0, increment: 0.01, prompt: "Specify spacing between bars at the same category axis location, as a proportion of the width of a single bar."}));
        var qMarkerBorderWidth = form.numericUpDown({name: "formMarkerBorderWidth", label: "Border width", default_value: 0, prompt: "Width of border around bars in pixels"});
        controls.push(qMarkerBorderWidth);
        if (qMarkerBorderWidth.getValue() > 0)
        {
            controls.push(form.numericUpDown({name: "formMarkerBorderOpacity", label: "Border opacity", minimum: 0, maximum: 1, increment: 0.1, required: false}));
            controls.push(form.colorPicker({name: "formMarkerBorderColor", label: "Border color", default_value: "#000000"}));
        }
    }
}

if (DATA_SERIES != null && !(chartType == "Bar Pictograph" && picto_icon == custom_icon_label))
{
    if (allow_control_groups)
        form.group({label: "DATA SERIES", expanded: true});
    if (DATA_SERIES.indexOf('Colors') > -1)
    {
        if (!isStacked && (chartType == "Bar" || chartType == "Column"))
        {
            var qMultColor = form.checkBox({name: "formMultiColorSeries", label: "Multiple colors within a single series", default_value: false});
            controls.push(qMultColor);
        }
        //palettes is chartType specific
        var palettes_extra = palettes.slice(0);
        palettes_extra.push("Custom palette (color pickers)");
        palettes_extra.push("Custom palette (R output)");
        colorPaletteControls("", "", palettes_extra, controls, ["Geographic Map", "Heat", "Scatter"].indexOf(chartType) == -1);
    }

    if (DATA_SERIES.indexOf('SubsliceColors') > -1)
        colorPaletteControls("Subslice", " of outer ring", line_subslice_palletes, controls);
    if (DATA_SERIES.indexOf("FillOpacity") > -1)
        controls.push(form.numericUpDown({name: "formFillOpacity", label: "Opacity", required: false, minimum: 0.0, maximum: 1.0, increment: 0.1, prompt: "Opacity is determined automatically based on chart settings. Click on up/down arrow to adjust transparency"}));
    if (DATA_SERIES.indexOf("MarkerSize") > -1)
        controls.push(form.numericUpDown({name: "formMarkerSize", label: "Marker size", default_value: 6, minimum: 1}));
    if (DATA_SERIES.indexOf("LineShape") > -1)
        controls.push(form.comboBox({name: "formLineShape", label: "Line shape", alternatives: ["Straight", "Curved"], default_value: "Straight", required: true}));
    if (DATA_SERIES.indexOf("LineType") > -1)
        controls.push(form.comboBox({name: "formLineType", label: "Line type", alternatives: ["Solid", "Dot", "Dash"], default_value: "Solid"}));
    
    if (DATA_SERIES.indexOf("LineThickness") > -1)
    {
        default_thickness = 3.0; // plotly defaults
        if (chartType == "Time Series")
        {
            default_thickness = 1.0;
            var qLineThick = form.numericUpDown({name: "formLineThickness", label: "Line thickness", default_value: default_thickness, minimum: 0.0, maximum: 20.0, increment: 0.5});
        }
        else
            var qLineThick = form.textBox({name: "formLineThickness", label: "Line thickness", default_value: default_thickness, required: false, prompt: "e.g. '1, 2, 3'"});
        controls.push(qLineThick);

        if (DATA_SERIES.indexOf("LineMarkers") > -1)
        {
            var qLineMarkerShow = form.checkBox({name: "formMarkerShow", label: "Show markers on line", default_value: false});
            controls.push(qLineMarkerShow);
            if (qLineMarkerShow.getValue())
            {
                if (chartType != "Radar")
                    controls.push(form.checkBox({name: "formMarkerShowAtEnds", label:" Show markers at end points only", default_value: false})); 

                controls.push(form.comboBox({name: "formMarkerSymbols", label: "Marker symbols", alternatives: marker_symbols, default_value: "circle"}));
                controls.push(form.numericUpDown({name: "formMarkerSize", label: "Marker size", default_value: 6, minimum: 1}));

            }
        }

    }
    if (show_second_yaxis)
    {
        colorPaletteControls("Val2", " of secondary data", palettes, controls);
        controls.push(form.comboBox({name: "formVal2LineShape", label: "Line shape of secondary data", alternatives: ["Straight", "Curved"], default_value: "Straight", required: true}));
        controls.push(form.comboBox({name: "formVal2LineType", label: "Line type of secondary data", alternatives: ["Solid", "Dot", "Dash"], default_value: "Solid"}));
        controls.push(form.textBox({name: "formVal2LineThickness", label: "Line thickness of secondary data", default_value: "3.0", required: false, prompt: "e.g. '1, 2, 3'"}));

        var qVal2LineMarkerShow = form.checkBox({name: "formVal2MarkerShow", label: "Show markers on line", default_value: false});
        controls.push(qVal2LineMarkerShow);
        if (qVal2LineMarkerShow.getValue())
        {
            controls.push(form.checkBox({name: "formVal2MarkerShowAtEnds", label: "Show markers at end points only", default_value: false}));
            controls.push(form.comboBox({name: "formVal2MarkerSymbols", label: "Marker symbols", alternatives: marker_symbols, default_value: "circle"}));

            controls.push(form.numericUpDown({name: "formVal2MarkerSize", label: "Marker size", default_value: 6, minimum: 1}));
        }
    }
}

if (typeof FITLINE != 'undefined' && FITLINE != null && !isStacked)
{
    if (allow_control_groups)
        form.group("TREND LINES");
    if (FITLINE.indexOf('FitLine') > -1)
    {
        var qFit = form.comboBox({name: "formFit", label: "Line of best fit", alternatives: ["None", "Linear", "LOESS", "Friedman's super smoother", "Cubic spline", "Moving average", "Centered moving average"], default_value: "None", prompt: "Add trend line using linear regression or a scatterplot smoother"});
        controls.push(qFit);
        var fitOpt = qFit.getValue();
        var fitCIOpt = false;
        if (["LOESS", "Cubic spline", "Linear"].indexOf(fitOpt) > -1)
        {
            var qFitCI = form.checkBox({name: "formFitCI", label: "Show 95% confidence interval"});
            controls.push(qFitCI);
            fitCIOpt = qFitCI.getValue();
        }
        if (["Moving average", "Centered moving average"].indexOf(fitOpt) > -1)
            controls.push(form.numericUpDown({name: "formFitWindow", label: "Window size", default_value: 3, minimum: 1, maximum: 10000}));    
        
        if (fitOpt != "None")
        {
            controls.push(form.checkBox({name: "formFitIgnoreLast", label: "Ignore last data point", default_value: false, prompt: "Do not use last data point to compute line of best fit"}));
            controls.push(form.comboBox({name: "formFitLineType", label: "Line type", alternatives: ["dot", "dash", "longdash", "dashdot", "solid"], default_value: chartType == "Line" ?"dot" : "solid"}));
            controls.push(form.numericUpDown({name: "formFitLineWidth", label: "Line width", default_value: 2, minimum: 1, maximum: 20}));
            colorPaletteControls("Fit", " of fit lines", line_subslice_palletes, controls);
            controls.push(form.numericUpDown({name: "formFitOpacity", label: "Opacity of fit lines", default_value: 1.0, minimum: 0.0, maximum: 1.0, increment: 0.1}));

            if (fitCIOpt)
            {
                colorPaletteControls("FitCI", " of fit CIs", line_subslice_palletes, controls);
                var qFitCIOpacity = form.numericUpDown({name: "formFitCIOpacity", label: "Opacity of CI", default_value: 0.4, minimum: 0.0, maximum: 1.0, increment: 0.1, prompt: "Control transparency of confidence interval ribbon"});
                controls.push(qFitCIOpacity);
            }
        }
    }
}


if (FONT != null)
{
    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)
    {
        if (FONT.indexOf('GlobalFontFamily') > -1)
        {
            var qGlobalFontFamily = form.comboBox({name: "formFont", 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();
        }
        if (FONT.indexOf('GlobalFontColor') > -1 && !(chartType == "Bar Pictograph" && labColAsIcon))
        {
            if (!allow_control_groups)
            {
                var qGlobalFontColorLabel = form.newLabel("Global font color");
                controls.push(qGlobalFontColorLabel);
            }
            var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: !allow_control_groups ? "" : "Global font color", default_value: DEFAULT_FONT_COLOR});
            controls.push(qGlobalFontColor);
            globalFontColor = qGlobalFontColor.getValue();
        }
        if (FONT.indexOf('GlobalFontSize') > -1)
        {
            var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 8, increment: 0.5});
            controls.push(qGlobalFontSize);
            globalFontSize = qGlobalFontSize.getValue();
        }
        if (FONT.indexOf('FontUnits') > -1)
        {
            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);
        }
    }
}

// Add annotation overlay group
if (dtype == "table")
{
    if (chartType == "Column")
        annotOverlayControls(controls, null, chartType);
    else if (chartType == "Radar")
        annotOverlayControls(controls, null, chartType, autoColor = true);
}

if (DATA_LABELS != null)
{
    if (allow_control_groups)
        form.group("DATA LABELS");
    var datalabshow = true;
    if (DATA_LABELS.indexOf('DataLabelPosition') > -1)
    {
        var qLabelPos = form.comboBox({name:"formDataLabelPosition", label: "Show data label", alternatives: ["No", "Next to bar", "Below row label", "Above row label", "Below icons", "Above icons"], default_value: "Next to bar"});
        controls.push(qLabelPos);
        var dLabOpt = qLabelPos.getValue();
        if (dLabOpt == "No")
            datalabshow = false;
        if (dLabOpt == "Below icons" || dLabOpt == "Above icons")
        {
            var qLabelHAlign = form.comboBox({name:"formDataLabelHorizAlign", label:" Data label horizontal alignment", alternatives: ["Default", "Left", "Center", "Right"], default_value: "Default"});
            controls.push(qLabelHAlign);
        }
    }
    if (DATA_LABELS.indexOf('DataLabelShow') > -1)
    {
        var qDatalabShow = form.checkBox({name: "formDataLabelShow", label: "Show data labels", default_value: false, prompt: "Show values of data points on the chart"});
        controls.push(qDatalabShow);
        var datalabshow = qDatalabShow.getValue();
        if (datalabshow && isStacked)
        {
            if (chartType == "Bar" || chartType == "Column")
                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"}));

            if (chartType == "Column")
                controls.push(form.checkBox({name: "formDataLabelCenter", label: "Vertically center data labels", default_value: true}));
        }
        if (datalabshow && chartType == "Line")
            controls.push(form.checkBox({name: "formDataLabelShowAtEnds", label: "Show data labels at ends only", default_value: false}));

    }
    if (!datalabshow && APPEARANCE.indexOf("AdditionalColumns") > -1 && (!isEmpty(qLeftCol)|| !isEmpty(qRightCol)))
    {
        // Allow left and right columns in Heatmaps to be formatted even if datalabels not shown
        DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont'];
        datalabshow = true; 
    }
    if (datalabshow && DATA_LABELS.indexOf('DataLabelFont') > -1)
    {
        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);
        }

        if (DATA_LABELS.indexOf('DataLabelFontMulticolor') > -1)
        {
            var hasAutocolor = isStacked || chartType == "Line" || chartType == "Funnel" || chartType == "Radar" || chartType == "Scatter" || chartType == "Venn";
            var altcols = ['Automatically', 'In a single color', 'In different colors for each series'];
            if (chartType == "Scatter")
                altcols.pop();
            if (!hasAutocolor)
                altcols.shift();
            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")
            {
                if (!allow_control_groups)
                {
                    var qDataLabelFontColorLabel = form.newLabel("Data label font color");
                    controls.push(qDataLabelFontColorLabel);
                }
                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);
            }
        }
        else if (!datalabFontDefault && !(chartType == "Bar Pictograph" && labColAsIcon) && !(chartType == "Heat"))
        {
            if (!allow_control_groups)
            {
                var qDataLabelFontColorLabel = form.newLabel("Data label font color");
                controls.push(qDataLabelFontColorLabel);
            }
            var qDatalabFont = form.colorPicker({name: "formDataLabelFontColor", label: !allow_control_groups ? "" : "Data label font color", default_value: globalFontColor});
            controls.push(qDatalabFont);
        }
        if (!datalabFontDefault)
        {
            qDataLabelSize = form.numericUpDown({name: "formDataLabelFontSize", label: "Data label font size", default_value: 8, increment: 0.5});
            controls.push(qDataLabelSize);
        }
    }
    if (datalabshow && DATA_LABELS.indexOf('DataLabelFormat') > -1)
    {
        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);
    }
    if (datalabshow && (DATA_LABELS.indexOf('DataLabelDecimals') > -1 || (DATA_LABELS.indexOf('DataLabelFormat') > -1)))
    {
        if (!allow_control_groups) 
        {
            var qDataLabDecimals = form.numericUpDown({name: "formDataLabelDecimals", label: "Decimal places", default_value: 0, increment: 1, minimum: 0, maximum: 12, required: false});
        }
        else
            var qDataLabDecimals = form.numericUpDown({name: "formDataLabelDecimals", label: "Decimal places", increment: 1, minimum: 0, maximum: 12, required: false});
        controls.push(qDataLabDecimals);
    }
    if (datalabshow && DATA_LABELS.indexOf('DataLabelPrefix') > -1)
    {
        var qDLabPrefix = form.textBox({name:'formPrefix', label: 'Custom prefix', required: false, prompt: "Optional text to prepend to the data labels"});
        controls.push(qDLabPrefix);
    }
    if (datalabshow && DATA_LABELS.indexOf('DataLabelSuffix') > -1)
    {
        var qDLabSuffix = form.textBox({name:'formSuffix', label: 'Custom suffix', required: false, prompt: "Optional text to append to the data labels"});
        controls.push(qDLabSuffix);
    }
    if (DATA_LABELS.indexOf('DataLabelAutoPosition') > -1)
        controls.push(form.checkBox({name: "formScatterLabelAutoPlacement", label: "Automatically position data labels", default_value: true, prompt: "Automatically positions data labels to reduce overlap between labels and points"}));
    if (DATA_LABELS.indexOf('ScatterMaxLab') > -1)
        controls.push(form.numericUpDown({name: "formScatterMaxLab", label: "Maximum data labels to plot", default_value: 50, maximum: 200, minimum: 0, prompt: "Hide some data labels if there are too many points"}));
    if (datalabshow && ANNOTATIONS != null) 
    {
        annotControls(controls, ANNOTATIONS, null);
    }

    if (show_second_yaxis)
    {
        var qVal2DataLabelShow = form.checkBox({name: "formVal2DataLabelShow", label: "Show data labels for secondary data", default_value: false});
        controls.push(qVal2DataLabelShow);
        if (qVal2DataLabelShow.getValue())
        {
            controls.push(form.checkBox({name: "formVal2DataLabelShowAtEnds", label: "Show data labels at end points only", default_value: false}));
            numberFormatControls("Val2DataLabel", "Data label", controls, values_number_formats); 
            fontControls("Val2DataLabel", "Data label", controls, 8, true, true);
        }
    }
}
if (chartType == "Scatter" && !isLabeled)
{
    // For scatterplots, annotations are not conditional on 
    // data labels being shown
    if (allow_control_groups)
        form.group ("Annotations")
    annotControls(controls,  ["", "Arrow - up", "Arrow - down", "Caret - up", "Caret - down", "Border", "Hide", "Marker border", "Shadow", "Text - after data label", "Text - before data label"], null, useDataVar = dtype == "variables")
}


if (GRIDLINES != null)
{
    if (allow_control_groups)
        form.group("GRID LINES");
    if (GRIDLINES.indexOf('ShowGrid') > -1)
        controls.push(form.checkBox({name: "formShowGrid", label: "Show grid lines", default_value: true}));
}
if (LEGEND != null)
{
    if (allow_control_groups)
        form.group("LEGEND");
    var hasleg = 1;
    if (LEGEND.indexOf('LegendShow') > -1)
    {
        if (LEGEND.indexOf('LegendShowAuto') > -1)
        {
            var qLegShowAuto = form.comboBox({name: "formLegendShow", label: "Legend", alternatives: ["Automatic", "Show", "Hide"], default_value: "Automatic"});
            controls.push(qLegShowAuto);
            hasleg = qLegShowAuto.getValue() != "Hide";
        } 
        else
        {
            var qLegShow = form.checkBox({name: "formLegendShow", label: "Show legend (if applicable)", default_value: true});
            controls.push(qLegShow);
            hasleg = qLegShow.getValue();
        }
    }

    if (hasleg && LEGEND.indexOf('LegendTitle') > -1)
        controls.push(form.textBox({label: "Legend title", type: "text", name: "formLegendTitle", required: false}));
    if (hasleg && LEGEND.indexOf('LegendFont') > -1)
        fontControls("Legend", "Legend", controls)
    
    var legendVertical = true;
    if (hasleg && LEGEND.indexOf('LegendOrientation') > -1)
    {
        qLegendOrnt = form.comboBox({name: "formLegendOrientation", label: "Legend orientation", alternatives: ['Vertical', 'Horizontal'], default_value: chartType == 'Time Series' ? 'Horizontal' : 'Vertical', prompt: "Position legend items beneath one another (vertical) or next to each other (horizontal). Note that with horizontal positioning, legend items will be placed beneath the previous ones if the width of the chart is not large enough."});
        controls.push(qLegendOrnt);
        legendVertical = qLegendOrnt.getValue() == "Vertical";
    }
    if (hasleg && LEGEND.indexOf('LegendWrap')) // not timeseries
    {
        var qLegendWrap = form.checkBox({name: "formLegendWrap", label: "Wrap legend", default_value: legendVertical ? true : false});
        controls.push(qLegendWrap);
        if (qLegendWrap.getValue())
        {
            var qLegendWrapN = form.numericUpDown({name: "formLegendWrapNchar", label: "Legend width (in characters)", default_value: 30, minimum: 5, increment: 5, maximum: 5000});
            controls.push(qLegendWrapN);
        }
    }
    if (hasleg && LEGEND.indexOf('LegendXPos') > -1)
    {
        if (chartType == "Time Series")
            var qLegX = form.numericUpDown({name: "formLegendXPos", label: "Horizontal placement", default_value: 1.0, increment: 0.02, minimum: 0.0, maximum: 1.0, prompt: "Choose whether legend should go across the whole chart (0.1) or just the right-most portion (1.0)"});
        else
        {
            var xpos = 1.02;
            if (!legendVertical)
                xpos = 0.5;
            else if (show_second_yaxis)
                xpos = 1.15;
            var qLegX = form.numericUpDown({name: "formLegendXPos", label: "Horizontal placement", default_value: xpos, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (far left) to 3 (far right)"});
        }
        controls.push(qLegX);
    }
    if (hasleg && LEGEND.indexOf('LegendYPos') > -1)
    {
        var ypos = 1.0;
        if (!legendVertical && show_second_yaxis)
            ypos = -0.3;
        else if (!legendVertical)
            ypos = -0.2;

        var qLegY = form.numericUpDown({name: "formLegendYPos", label: "Vertical placement", default_value: ypos, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (below) and 3 (above)"});
        controls.push(qLegY);
    }
    if (LEGEND.indexOf('LegendBubbles') > -1)
        controls.push(form.checkBox({name: "formLegendBubblesShow", label: "Show bubble legend (if applicable)", default_value: true}));
}
if (TITLE != null)
{
    if (allow_control_groups)
        form.group('Title');
    if (TITLE.indexOf('Title') > -1)
        formattedTextControls("Title", "Title", controls, 12, "", true, false, 20, true, TITLE.indexOf('HAlign') > -1);
    if (show_as_small_mult)
    {
        var qSmallMultTitle = form.checkBox({name:"formSmallMultTitle", label:"Show panel titles", default_value: true, prompt: "Show series or group name above each panel"});
        controls.push(qSmallMultTitle);
        if (qSmallMultTitle.getValue())
            formattedTextControls("SmallMultTitle", "Panel title", controls, 10, "", true, true, 20, false, false);
    }
    if (TITLE.indexOf('Subtitle') > -1)
        formattedTextControls("Subtitle", "Subtitle", controls, 8, "", true, false, 20, true, TITLE.indexOf('HAlign') > -1);
    if (TITLE.indexOf('Footer') > -1)
        formattedTextControls("Footer", "Footer", controls, 6, "Optional footer. Leave blank to show default text", true, ["Heat", "Pie", "Donut"].indexOf(chartType) == -1, 100, true, TITLE.indexOf('HAlign') > -1);
}

if (CATEGORIES_AXIS != null)
{
    var axisPrefix =  "Axis";
    if (allow_control_groups)
        form.group(categoriesAxisLabel);
    else
        axisPrefix = categoriesAxisLabel.charAt(0).toUpperCase() + categoriesAxisLabel.substr(1).toLowerCase();

    var labelShowX = "Show " + axisPrefix.toLowerCase() + " labels";
    if (chartType == "Bar Pictograph")
        labelShowX = "Show bar labels";

    if (CATEGORIES_AXIS.indexOf('CategoriesMin') > -1)
    {
        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."}));
    }
    if (CATEGORIES_AXIS.indexOf('CategoriesMax') > -1)
    {
        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."}));
    }
    if (CATEGORIES_AXIS.indexOf('CategoriesGrid') > -1)
    {
        var qXGridWidth = form.numericUpDown({name: "formCategoriesGridWidth", label: "Grid line width", minimum: 0, default_value: (chartType == "Radar" || chartType == "Scatter") ? DEFAULT_GRID_WIDTH : 0, increment: 0.5, prompt: "Set to zero to hide line"});
        controls.push(qXGridWidth);
        if (qXGridWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formCategoriesGridColor", label: "Grid line color", default_value: DEFAULT_GRID_COLOR}));
    } 
    if (CATEGORIES_AXIS.indexOf('CategoriesLine') > -1)
    {
        var qXLineWidth = form.numericUpDown({name: "formCategoriesLineWidth", label: "Axis line width", minimum: 0, default_value: 0, increment: 0.5, prompt: "Set to zero to hide line"});
        controls.push(qXLineWidth);
        if (qXLineWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formCategoriesLineColor", label: "Axis line color", default_value: DEFAULT_AXIS_COLOR}));
    }
    
    var showX = true;
    if (CATEGORIES_AXIS.indexOf('CategoriesAxisShow') > -1)
    {
        qShowX = form.checkBox({name: "formCategoriesAxisShow", label: labelShowX, default_value: true});
        showX = qShowX.getValue();
        controls.push(qShowX);
    }

    if (chartType == "Stream")
    {
        var qTickUnits = form.comboBox({name: "formCategoriesTickUnits", label: axisPrefix + " tick units", alternatives: ["Automatic", "Number", "Day", "Month", "Year"], default_value: "Automatic"});
        controls.push(qTickUnits);
        var qAutoTickDist = form.checkBox({name: "formCategoriesAutoTickInterval", label: "Automatic tick interval", default_value: true});
        var auto_tick_distance = qAutoTickDist.getValue();
        controls.push(qAutoTickDist);
        if (!auto_tick_distance)
        {
            var qTickIntervals = form.numericUpDown({name: "formCategoriesTickInterval", label: axisPrefix + " tick interval", default_value: 3, increment: 1, minimum: 1, maximum: 1000000, prompt: "Number of tick units between each tick label. The value may be rounded for more balanced tick labels."});
            controls.push(qTickIntervals);
        }
    }
   
    // Allow axis format to be changed even if it is not shown
    // e.g. switching between date and categorical axis.
    if (CATEGORIES_AXIS.indexOf('CategoriesNumberFormat') > -1)
        numberFormatControls("Categories", axisPrefix, controls, categories_number_formats, CATEGORIES_AXIS.indexOf('CategoriesPrefix') > -1, showX)
    if (showX)
    {
        if (CATEGORIES_AXIS.indexOf('CategoriesTickInterval') > -1)
            controls.push(form.numericUpDown({name: "formCategoriesTickInterval", label: axisPrefix + " tick interval", default_value: 1, increment: 1, minimum: 1, maximum: 1000}));
        if (CATEGORIES_AXIS.indexOf('CategoriesTickFont') > -1)
            fontControls("CategoriesTick", axisPrefix + " label", controls, 9, adjustColor = !(chartType == "Bar Pictograph" && labColAsIcon));
        if (CATEGORIES_AXIS.indexOf('CategoriesTickHorizAlign') > -1)
        {
            controls.push(form.comboBox({name:"formCategoriesTickHorizAlign", label: "Horizontal alignment", alternatives: ["Default", "Left", "Center", "Right"], default_value: "Default"})); 
        }
        var xtickrotated = false;
        if (CATEGORIES_AXIS.indexOf('CategoriesTickAngle') > -1)
        {
            qXTickAngle = form.comboBox({name: "formCategoriesTickAngle", label: axisPrefix + " label orientation", alternatives: ["Automatic", "Horizontal", "Vertical (clockwise)", "Vertical (counter-clockwise)", "Diagonal"], default_value: "Automatic"});
            controls.push(qXTickAngle);
            xtickrotated = !(qXTickAngle.getValue() == "Automatic" || qXTickAngle.getValue() == "Horizontal");
        }
        if (CATEGORIES_AXIS.indexOf('LabelWrap') > -1)
        {
            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: 1, increment: 5, maximum: 1000}));
        }   
        if (CATEGORIES_AXIS.indexOf('CategoriesTickMaxNum') > -1)
            controls.push(form.numericUpDown({name: "formCategoriesTickMaxNum", label: "Maximum number of ticks", required: false, minimum: 1, prompt: "Specify an upper limit to the number of tick labels shown. Note the number of ticks shown may be lower than this."})); 
        if (CATEGORIES_AXIS.indexOf('CategoriesTickLen') > -1)
        {
            var xticklength = 3;
            if (["Box", "Bean", "Density", "Histogram", "Violin"]. indexOf(chartType) != -1)
                xticklength = 20;
            else if (xtickrotated)
                xticklength = 0;
            controls.push(form.numericUpDown({name: "formCategoriesTickLen", label: "Tick mark length", default_value: xtickangle = xticklength, prompt: "Length of tick marks, or distance of tick labels from axis, in pixels"}));
        }
        if (CATEGORIES_AXIS.indexOf('CategoriesTickColor') > -1)
            controls.push(form.colorPicker({name: "formCategoriesTickColor", label: "Tick mark color", default_value: "transparent"}));
    }
    if (CATEGORIES_AXIS.indexOf('CategoriesTitle') > -1)
    {
        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)
    }

}
if (VALUES_AXIS != null)
{
    var axisPrefix = "Axis";
    if (!allow_control_groups)
        axisPrefix = valuesAxisLabel.charAt(0).toUpperCase() + valuesAxisLabel.substr(1).toLowerCase();
    var labelShowY = "Show " + axisPrefix.toLowerCase() + " labels";

    if (allow_control_groups && chartType == "Heat")
        form.group("Color scale");
    else if (allow_control_groups)
        form.group(valuesAxisLabel);

    if (VALUES_AXIS.indexOf('ValuesMin') > -1)
        controls.push(form.textBox({name: "formValuesMin", label: "Minimum value", required: false, prompt: "Leave blank to determine automatically from input data."}));
    if (VALUES_AXIS.indexOf('ValuesMax') > -1)
        controls.push(form.textBox({name: "formValuesMax", label: "Maximum value", required: false, prompt: "Leave blank to determine automatically from input data."}));

    // For Heat maps, this is the real beginning of the Y-axis group
    if (allow_control_groups && chartType == "Heat")
        form.group(valuesAxisLabel);
   
    if (VALUES_AXIS.indexOf('ValuesGrid') > -1)
    {
        var qYGridWidth = form.numericUpDown({name: "formValuesGridWidth", label: "Grid line width", minimum: 0, default_value: DEFAULT_GRID_WIDTH, increment: 0.5, prompt: "Set to zero to hide line"});
        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}));
    } 
    if (VALUES_AXIS.indexOf('ValuesLine') > -1)
    {
        var qYLineWidth = form.numericUpDown({name: "formValuesLineWidth", label: "Axis line width", minimum: 0, default_value: 0, increment: 0.5, prompt: "Set to zero to hide line"});
        controls.push(qYLineWidth);
        if (qYLineWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formValuesLineColor", label: "Axis line color", default_value: DEFAULT_AXIS_COLOR}));
    }
    if (VALUES_AXIS.indexOf('ValuesZeroLine') > -1)
    {
        var qY0LineWidth = form.numericUpDown({name: "formValuesZeroLineWidth", label: "Zero line width", minimum: 0, default_value: ['Area', 'Bar', 'Column', 'Line'].indexOf(chartType) > -1 ? DEFAULT_AXIS_WIDTH : 0, increment: 0.5, prompt: "Set to zero to hide line shown at values = 0"});
        controls.push(qY0LineWidth);
        if (qY0LineWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formValuesZeroLineColor", label: "Zero line color", default_value: DEFAULT_AXIS_COLOR}));
    }

    var showY = true;
    if (VALUES_AXIS.indexOf('ValuesAxisShow') > -1)
    {
        qShowY = form.checkBox({name: "formValuesAxisShow", label: labelShowY, default_value: true});
        controls.push(qShowY);
        showY = qShowY.getValue();
    } 
    // Allow formatting to be changed even if axis is not show
    // use for e.g. switching between date and categorical axis
    if (VALUES_AXIS.indexOf('ValuesNumberFormat') > -1)
        var yNumberType = numberFormatControls("Values", axisPrefix, controls, values_number_formats, VALUES_AXIS.indexOf('ValuesPrefix') > -1, showY)
    if (showY) 
    {
        if (VALUES_AXIS.indexOf('ValuesNumberTicks') > -1) //Weird parameter for Stream Graph
            controls.push(form.numericUpDown({name: "formValuesTickMaxNum", label: "Maximum number of ticks", default_value: 5, increment: 1, minimum: 1, maximum: 1000}));
        if (VALUES_AXIS.indexOf('ValuesTickFont') > -1)
            fontControls("ValuesTick", axisPrefix + " label", controls, 9);
        if (VALUES_AXIS.indexOf('ValuesTickMaxNum') > -1)
            controls.push(form.numericUpDown({name: "formValuesTickMaxNum", label: "Maximum number of ticks", required: false, minimum: 1, prompt: "Specify an upper limit to the number of tick labels shown. Note the number of ticks shown may be lower than this."}));
        if (VALUES_AXIS.indexOf('ValuesTickLen') > -1)
            controls.push(form.numericUpDown({name: "formValuesTickLen", label: "Tick mark length", default_value: 0, prompt: "Length of tick marks, or distance of tick labels from axis, in pixels"}));
        if (VALUES_AXIS.indexOf('ValuesTickColor') > -1)
            controls.push(form.colorPicker({name: "formValuesTickColor", label: "Tick mark color", default_value: "transparent"}));
    }
    if (VALUES_AXIS.indexOf('ValuesTitle') > -1)
    {
        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);
    }

}

if (show_second_yaxis)
{
    var axisPrefix = "Axis";
    if (allow_control_groups)
        form.group("Second Values (Y) axis");

    controls.push(form.textBox({name: "formVal2Min", label: "Minimum value", required: false, prompt: "Leave blank to determine automatically from input data."}));
    controls.push(form.textBox({name: "formVal2Max", label: "Maximum value", required: false, prompt: "Leave blank to determine automatically from input data."}));
    var qY2LineWidth = form.numericUpDown({name: "formVal2LineWidth", label: "Axis line width", minimum: 0, default_value: 0});
    controls.push(qY2LineWidth);
    if (qY2LineWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formVal2LineColor", label: "Axis line color", default_value: "#000000"}));

        var qShowY2 = form.checkBox({name: "formVal2AxisShow", label: "Show axis labels", default_value: true});
    controls.push(qShowY2);
    if (qShowY2.getValue()) 
    {
        numberFormatControls("Val2", axisPrefix, controls, values_number_formats);
        fontControls("Val2Tick", axisPrefix + " tick label", controls, 9);
        controls.push(form.numericUpDown({name: "formVal2TickMaxNum", label: "Maximum number of ticks", required: false, minimum: 1, prompt: "Specify an upper limit to the number of tick labels shown. Note the number of ticks shown may be lower than this."}));
        controls.push(form.numericUpDown({name: "formVal2TickLen", label: "Tick mark length", default_value: 0, prompt: "Length of tick marks, or distance of tick labels from axis, in pixels"}));
        controls.push(form.colorPicker({name: "formVal2TickColor", label: "Tick mark color", default_value: "transparent"}));
    }
    var qShowVal2Title = form.checkBox({name: "formShowVal2Title", label: "Show second y-axis title", default_value: true});
    controls.push(qShowVal2Title);
    if (qShowVal2Title.getValue())
        formattedTextControls("Val2Title", "Axis title", controls, 10, "Leave blank to read axis title from input data", true);


}

if (HOVER != null)
{
    if (allow_control_groups)
        form.group("HOVER");
    var showHover = true;
    if (HOVER.indexOf('HoverShow') > -1)
    {
        var qShowHover = form.checkBox({name: "formHoverShow", label: "Show hover text", default_value: true});
        showHover = qShowHover.getValue();
        controls.push(qShowHover);
    }
    if (showHover && HOVER.indexOf('HoverThreshold') > -1)
    {
        controls.push(form.numericUpDown({name: "formHoverThreshold", label: "Threshold to show hover instead of labels (%)", default_value: 0.3, minimum: 0.0, maximum: 100, increment: 0.1, prompt: "Data labels will be hidden and shown as hover text instead in segments which are smaller than the threshold"}));
    }
    if (showHover && HOVER.indexOf('HoverNumberFormat') > -1 )
    {
        var hover_default_format = (typeof dataLabelFormat != 'undefined') ? dataLabelFormat : hover_number_formats[0];
        if (chartType == "Time Series")
            hover_default_format = yNumberType;
        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);
    }
    if (showHover && HOVER.indexOf('HoverPrefixSuffix') > -1 )
    {
        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);
    }
    if (showHover && HOVER.indexOf('HoverFontFamily') > -1)
    {
        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(); 
    }
    if (showHover && !hoverFontDefault && HOVER.indexOf('HoverFontFamily') > -1)
    {
        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);
    }
    if (showHover && (!hoverFontDefault || chartType == "Pie" || chartType == "Donut") && HOVER.indexOf('HoverFontColor') > -1)
    {
        var is_pie_chart  = chartType == "Pie" || chartType == "Donut";
        if (is_pie_chart)
        {
            var qHoverFontAutoColor = form.checkBox({name: "formHoverAutoFontColor", label: "Automatically determine font color", default_value: true, prompt: "Use black or white text depending on the hover background color"});
            controls.push(qHoverFontAutoColor);
        }
        if (!is_pie_chart || !qHoverFontAutoColor.getValue())
        {
            var qHoverFontColor = form.colorPicker({name: "formHoverFontColor", label: "Hovertext font color", default_value: chartType == "Pie" || chartType == "Donut" ? "#EFEFEF" : globalFontColor});
            controls.push(qHoverFontColor);
        }
    }
    if (showHover && !hoverFontDefault && HOVER.indexOf('HoverFontSize') > -1)
    {
        var qHoverFontSize = form.numericUpDown({name: "formHoverFontSize", label: "Hovertext font size", default_value: 9}); 
        controls.push(qHoverFontSize);
    }
    if (HOVER.indexOf('HoverBgColor') > -1)
    {
        var qHoverBgAutoColor = form.checkBox({name: "formHoverBgAutoColor", label: "Automatically set background color", default_value: true});
        controls.push(qHoverBgAutoColor);
        if (!qHoverBgAutoColor.getValue())
            controls.push(form.colorPicker({name: "formHoverBgColor", label: "Background color", default_value: "#CCCCCC"}));
        controls.push(form.numericUpDown({name: "formHoverBgOpacity", label: "Opacity", default_value: 0.8, maximum: 1.0, increment: 0.05}));
    }

}
if (MARGINS != null)
{
    if (allow_control_groups)
        form.group("MARGINS");
    var qMarginContol = false; 
    if (MARGINS.indexOf('CustomMargin') > -1)
    {
        qCustomMargin = form.checkBox({name: "formCustomMargin", label: "Customize margins", default_value: false, prompt: "Select to manually specify margin sizes in pixels"});
        controls.push(qCustomMargin);
        qMarginControl = qCustomMargin.getValue();
        if (qMarginControl)
            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"}));
    }
    if (MARGINS.indexOf('Margin') > -1 || qMarginControl)
    {
        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 (BACKGROUND != null)
{
    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);
    var showBg = !qBg.getValue();
    if (showBg && BACKGROUND.indexOf('BgColor') > -1)
        controls.push(form.colorPicker({name: "formBgColor", label: "Background color", default_value: "#FFFFFF"}));
    if (showBg && BACKGROUND.indexOf('BgOpacity') > -1)
        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 6.1.1
library(flipFormat)
library(flipChart)
library(flipChartBasics)
template <- get0("formTemplate")

# Permit subscripted QTables to retain there attributes.
# Added to ensure older visualizations would not change
# suddenly when subcripting of QTables was released.
ALLOW.QTABLE.CLASS <- TRUE
# Subscripting is disabled by default in Q until printing vectors is fixed.
# It is re-enabled here only in Q for the visualization, unless the user wants to switch it off.
product.name <- get0("productName", mode = "character", envir = .GlobalEnv, ifnotfound = "Q")
if (product.name == "Q" && !exists("allowQTableSubscripting", envir = .GlobalEnv, mode = "function"))
    assign("allowQTableSubscripting", function() TRUE, envir = .GlobalEnv)

# Set defaults if no template
if (is.null(template))
{
    if (formChartType == "Geographic Map" && isTRUE(get0("formBackgroundMap")))
        default.palette <- "Reds, light to dark"
    else if (formChartType %in% c("Geographic Map", "Heat"))
        default.palette <- "Blues, light to dark"
    else
        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)))
}
default.grid.color = "#D2D8E1"

signif.p.cutoffs <- sapply(QSettings$StatisticalAssumptions$SigLevels, function(x) x$CutoffPValue)
signif.color.default <- if (isTRUE(get0("formDataLabelShow"))) get0("formDataLabelFontColor", ifnotfound = template$fonts$`Data labels`$color) else get0("formFontColor", ifnotfound = template$global.font$color)
signif.colors.pos <- rep(signif.color.default, length(signif.p.cutoffs))
signif.colors.neg <- rep(signif.color.default, length(signif.p.cutoffs))
signif.symbol <- "None"
if (isTRUE(grepl("Caret", get0("formSigTests"))))
    signif.symbol <- "Caret"
if (isTRUE(grepl("Arrow", get0("formSigTests"))))
    signif.symbol <- "Arrow"
signif.colors.on.font <- isTRUE(grepl("Font color", get0("formSigTests"), ignore.case = TRUE))
show.significance <- get0("formSigTests", ifnotfound = "No") != "No"
if (show.significance && isTRUE(grepl("color", get0("formSigTests"), ignore.case = TRUE)))
{
    signif.colors.pos <- sapply(QSettings$StatisticalAssumptions$SigLevels, function(x) x$ColorPos)
    signif.colors.neg <- sapply(QSettings$StatisticalAssumptions$SigLevels, function(x) x$ColorNeg)
}

show.labels = !get0("formNames", ifnotfound=TRUE)
input.data.raw = if (!exists("formX")) NULL else list(X = PrepareForCbind(get0("formX"),
       use.span = get0("formXSpan", ifnotfound = FALSE), show.labels = show.labels),
       Y = get0("formY"),
       Z1 = PrepareForCbind(get0("formZ"), use.span = get0("formVSizesSpan", ifnotfound = FALSE), show.labels = show.labels),
       Z2 = PrepareForCbind(get0("formZ2"), use.span = get0("formVColorsSpan", ifnotfound = FALSE), show.labels = show.labels),
       groups = PrepareForCbind(get0("formGroups"), use.span = get0("formVGroupsSpan", ifnotfound = FALSE), show.labels = show.labels),
       labels = get0("formScatterLabels"))

if (formChartType == "Scatter" && !is.null(input.data.raw))
{
    offset <- length(input.data.raw) + 1
    i <- 1
    while (exists(paste0("formAnnotType", i)))
    {
        if (exists(paste0("formAnnotDataVar", i)))
        {
            input.data.raw[[offset]] <- PrepareForCbind(get0(paste0("formAnnotDataVar", i)),
                                                        is.scatter.annot.data = TRUE)
            assign(paste0("formAnnotData", i), Labels(input.data.raw[[offset]]))
            offset <- offset + 1
        }
        i <- i + 1
    }
}

# Processing all the selections from the 'Inputs' and 'Charts' tab.
select.rows <- if (exists("formSelectRowsCtrl")) StringIsFromControl(formSelectRowsCtrl) else get0("formSelectRows")
select.cols <- if (exists("formSelectColsCtrl")) StringIsFromControl(formSelectColsCtrl) else get0("formSelectCols")
pd <- PrepareData(formChartType,  QFilter, QPopulationWeight, input.data.table = get0("formTable"),
                  input.data.raw = input.data.raw,
                  input.data.pasted = if (length(get0("formPastedData")) == 0) NULL else list(get0("formPastedData"), !get0("formNotDataFrame", ifnotfound = TRUE),  get0("formPastedColumnNames"), get0("formPastedRowNames")),
                  signif.append = show.significance, signif.symbol = signif.symbol, signif.symbol.size = get0("formSigTestsSize", ifnotfound=12), signif.p.cutoffs = signif.p.cutoffs, signif.colors.pos = signif.colors.pos, signif.colors.neg = signif.colors.neg, signif.colors.on.font = signif.colors.on.font,
                  first.aggregate = get0("formFirstAggregate", ifnotfound = FALSE),  group.by.last = get0("formGroupByLastColumn", ifnotfound = FALSE), tidy = get0("formTidy", ifnotfound = FALSE), tidy.labels = get0("formTidyLabels", ifnotfound = FALSE), transpose = get0("formTranspose"), row.names.to.remove = get0("formIgnoreRows"), column.names.to.remove = get0("formIgnoreCols"), hide.empty.rows = get0("formHideEmptyRows", ifnotfound = FALSE), hide.empty.columns = get0("formHideEmptyCols", ifnotfound = FALSE), select.rows = select.rows, first.k.rows = as.numeric(get0("formFirstKRows")), last.k.rows = as.numeric(get0("formLastKRows")), select.columns = select.cols, first.k.columns = as.numeric(get0("formFirstKCols")), last.k.columns = as.numeric(get0("formLastKCols")),
auto.order.rows = get0("formAutoOrderRows", ifnotfound=FALSE), sort.rows = get0("formSortRows", ifnotfound=FALSE), sort.rows.exclude = get0("formSortRowsExclude"), sort.rows.column = if (sum(nchar(get0("formSortRowsColumn"))) == 0) NULL else formSortRowsColumn, sort.rows.decreasing = get0("formSortRowsDecr"),
auto.order.columns = get0("formAutoOrderCols", ifnotfound=FALSE), sort.columns = get0("formSortCols"), sort.columns.exclude = get0("formSortColsExclude"), sort.columns.row = if (sum(nchar(get0("formSortColsRow"))) == 0) NULL else formSortColsRow, sort.columns.decreasing = get0("formSortColsDecr"), reverse.rows = get0("formReverseRows"), reverse.columns = get0("formReverseCols"), hide.output.threshold = if (isTRUE(get0("formHideOutput"))) as.numeric(get0("formHideOutputThres")) else 0, hide.values.threshold = if (isTRUE(get0("formHideValues"))) as.numeric(get0("formHideValuesThres")) else 0, hide.rows.threshold = if (isTRUE(get0("formHideRowsBySize"))) as.numeric(get0("formHideRowsThres")) else 0, hide.columns.threshold = if (isTRUE(get0("formHideColsBySize"))) as.numeric(get0("formHideColsThres")) else 0, as.percentages = get0("formAsPercentages", ifnotfound = FALSE), hide.percent.symbol = get0("formHidePercent", ifnotfound = FALSE), categorical.as.binary = get0("formCategoricalAsBinary"), show.labels = show.labels, date.format = get0("formDateFormat", ifnotfound = "Automatic"), scatter.mult.yvals = get0("formScatterMultYvals", ifnotfound = FALSE), row.labels = get0("formRowLabels"), column.labels = get0("formColumnLabels"))

# Determine default number of decimals to show
number.type <- "None"
if (!is.null(attr(pd$data, "statistic")))
{
    number.type <- attr(pd$data, "statistic")
} else if (all(c("questions", "name") %in% names(attributes(pd$data))))
{
    dn <- dimnames(pd$data)
    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)

# Specify colors
scatter.colors.column = if (is.null(pd$scatter.variable.indices["colors"])||is.na(pd$scatter.variable.indices["colors"])) pd$scatter.variable.indices["groups"] else pd$scatter.variable.indices["colors"]

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))
    }
}

if (formChartType == "Funnel")
    formChartType <- "Pyramid"
colors.main <- GetVectorOfColors(template, pd$data, QFilter, formChartType,
    scatter.colors.column, get0("formMultiColorSeries", ifnotfound = FALSE),
    get0("formPalette"), get0("formCustomColor"), get0("formCustomGradientStart"),
    get0("formCustomGradientEnd"), custom.palette, color.values = get0("formColorValues"),
    small.multiples = get0("formSmallMultiples", ifnotfound = FALSE))

data.statistic <- ""
if (!is.null(attr(pd$data, "statistic"))) {
    data.statistic <- attr(pd$data, "statistic")
} else if (is.array(pd$data) && !is.null(attr(pd$data, "questions"))) {
    dn <- dim(pd$data)
    data.statistic <- dimnames(pd$data)[[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)), data.is.percentage)
if (formChartType == "Scatter")
    pn$categories.number.format <- flipChartBasics::ChartNumberFormat(list(get0("formCategoriesNumberType"), get0("formCategoriesDateType"), get0("formCategoriesNumberCustom"), get0("formCategoriesSeparateThousands"), get0("formCategoriesDecimals")), data.is.percentage)

# Data for secondary axis - only used for Column charts
pd2 <- NULL
default.digits2 <- 2
if (!is.null(get0("formTable2")) || !is.null(get0("formPastedData2")) || !is.null(get0("formX2")))
{
    select.rows2 <- if (exists("formSelectRowsCtrl2")) StringIsFromControl(formSelectRowsCtrl2) else get0("formSelectRows2")
    select.cols2 <- if (exists("formSelectColsCtrl2")) StringIsFromControl(formSelectColsCtrl2) else get0("formSelectCols2")
    pd2 <- PrepareData(formChartType,  QFilter, QPopulationWeight, input.data.table = get0("formTable2"), input.data.raw = if (!exists("formX2")) NULL else list(X = get0("formX2"), Y = get0("formY2")),
    input.data.pasted = if (length(get0("formPastedData2")) == 0) NULL else list(get0("formPastedData2"), !get0("formNotDataFrame2", ifnotfound = TRUE),  get0("formPastedColumnNames2"), get0("formPastedRowNames2")), first.aggregate = get0("formFirstAggregate2", ifnotfound = FALSE),  group.by.last = get0("formGroupByLastColumn2", ifnotfound = FALSE), tidy = get0("formTidy2", ifnotfound = FALSE), tidy.labels = get0("formTidyLabels2", ifnotfound = FALSE), transpose = get0("formTranspose2"), row.names.to.remove = get0("formIgnoreRows2"), column.names.to.remove = get0("formIgnoreCols2"), hide.empty.rows = get0("formHideEmptyRows2", ifnotfound = FALSE), hide.empty.columns = get0("formHideEmptyCols2", ifnotfound = FALSE), select.rows = select.rows2, first.k.rows = as.numeric(get0("formFirstKRows2")), last.k.rows = as.numeric(get0("formLastKRows2")), select.columns = select.cols2, first.k.columns = as.numeric(get0("formFirstKCols2")), last.k.columns = as.numeric(get0("formLastKCols2")),
    auto.order.rows = get0("formAutoOrderRows2", ifnotfound=FALSE), sort.rows = get0("formSortRows2", ifnotfound=FALSE), sort.rows.exclude = get0("formSortRowsExclude2"), sort.rows.column = if (sum(nchar(get0("formSortRowsColumn2"))) == 0) NULL else formSortRowsColumn, sort.rows.decreasing = get0("formSortRowsDecr2"),
    auto.order.columns = get0("formAutoOrderCols2", ifnotfound=FALSE), sort.columns = get0("formSortCols2"), sort.columns.exclude = get0("formSortColsExclude2"), sort.columns.row = if (sum(nchar(get0("formSortColsRow2"))) == 0) NULL else formSortColsRow, sort.columns.decreasing = get0("formSortColsDecr2"), reverse.rows = get0("formReverseRows2"), reverse.columns = get0("formReverseCols2"), hide.output.threshold = if (isTRUE(get0("formHideOutput2"))) as.numeric(get0("formHideOutputThres2")) else 0, hide.rows.threshold = if (isTRUE(get0("formHideRowsBySize2"))) as.numeric(get0("formHideRowsThres2")) else 0, hide.columns.threshold = if (isTRUE(get0("formHideColsBySize2"))) as.numeric(get0("formHideColsThres2")) else 0, as.percentages = get0("formAsPercentages2", ifnotfound = FALSE), hide.percent.symbol = get0("formHidePercent2", ifnotfound = FALSE), categorical.as.binary = get0("formCategoricalAsBinary2"), show.labels = !get0("formNames2", ifnotfound=TRUE), date.format = get0("formDateFormat2", ifnotfound = "Automatic2"), scatter.mult.yvals = get0("formScatterMultYvals2", ifnotfound = FALSE), row.labels = get0("formRowLabels2"), column.labels = get0("formColumnLabels2"))

    number.type2 <- "None"
    if (!is.null(attr(pd2$data, "statistic")))
    {
        number.type2 <- attr(pd2$data, "statistic")
    } else if (all(c("questions", "name") %in% names(attributes(pd2$data))))
    {
        dn <- dimnames(pd2$data)
        number.type2 <- dn[[length(dn)]][1]
    }
    if (isTRUE(grepl("%", number.type2)))
        number.type2 <- "Percent"
    default.digits2 <- QAppearance$Number$DecimalPlaces[[number.type2]]
    if (is.null(default.digits2))
        default.digits2 <- QAppearance$Number$DecimalPlaces[["None"]]

}

# Collect list of annotations which may vary in length and composition
annot.list <- NULL
a.ind <- 1
if (exists("formAnnotThresType1"))
{
    while(sum(nchar(get0(paste0("formAnnotType", a.ind)))) > 0)
    {
        tmp <- list(type = get0(paste0("formAnnotType", a.ind)),
                    data = get0(paste0("formAnnotData", a.ind)),
                    threstype = get0(paste0("formAnnotThresType", a.ind)),
                    threshold = get0(paste0("formAnnotThreshold", a.ind)),
                    color = get0(paste0("formAnnotColor", a.ind)),
                    size = get0(paste0("formAnnotSize", a.ind)),
                    width = get0(paste0("formAnnotWidth", a.ind)),
                    offset = get0(paste0("formAnnotOffset", a.ind)),
                    shiftleft = get0(paste0("formAnnotShiftLeft", a.ind)),
                    shiftright = get0(paste0("formAnnotShiftRight", a.ind)),
                    format = get0(paste0("formAnnotFormat", a.ind)),
                    prefix = get0(paste0("formAnnotPrefix", a.ind)),
                    suffix = get0(paste0("formAnnotSuffix", a.ind)),
                    font.family = get0(paste0("formAnnotFontFamily", a.ind)),
                    font.weight = get0(paste0("formAnnotFontWeight", a.ind)),
                    font.style = get0(paste0("formAnnotFontStyle", a.ind)))
        annot.list[[a.ind]] <- tmp
        a.ind <- a.ind + 1
    }
}

# List of overlay annotations
overlay.annot.list <- NULL
a.ind <- 1
if (exists("formAnnotOverlayThresType1"))
{
    while(any(nchar(get0(paste0("formAnnotOverlayType", a.ind)))))
    {
        overlay.annot.list[[a.ind]] <- list(
                    type = get0(paste0("formAnnotOverlayType", a.ind)),
                    data = get0(paste0("formAnnotOverlayData", a.ind)),
                    custom.symbol = get0(paste0("formAnnotOverlayCustomSymbol", a.ind)),
                    threstype = get0(paste0("formAnnotOverlayThresType", a.ind)),
                    threshold = get0(paste0("formAnnotOverlayThreshold", a.ind)),
                    relative.pos = get0(paste0("formAnnotOverlayRelativePos", a.ind)),
                    halign = get0(paste0("formAnnotOverlayHAlign", a.ind)),
                    valign = get0(paste0("formAnnotOverlayVAlign", a.ind)),
                    offset = get0(paste0("formAnnotOverlayOffset", a.ind)),
                    color = get0(paste0("formAnnotOverlayColor", a.ind)),
                    size = get0(paste0("formAnnotOverlaySize", a.ind)),
                    format = get0(paste0("formAnnotOverlayFormat", a.ind)),
                    prefix = get0(paste0("formAnnotOverlayPrefix", a.ind)),
                    suffix = get0(paste0("formAnnotOverlaySuffix", a.ind)),
                    font.family = get0(paste0("formAnnotOverlayFontFamily", a.ind)))
        a.ind <- a.ind + 1
    }
}

# Creating the chart or table
# Strange line breaks is necessary for the R output to be named according to the R code
viz <- if(formChartType== "Table" && !formTableAutoFit) pd$data else if (formChartType == "Table" && formTableAutoFit) CreateCustomTable(pd$data,
    transpose = formTableTranspose,
    format.type = formTableFormatType,
    format.show.pct.sign = get0("formTablePercentSign", ifnotfound = TRUE),
    format.decimals = .ifNull(formTableDecimals, default.digits),
    global.font.family = formFont,
    global.font.color = formFontColor,
    font.unit = formFontUnits,
    show.col.headers = formColShowHead,
    col.widths = formTableColWidths,
    col.header.labels = get0("formTableColHeadLabels"),
    col.header.border.width = get0("formTableColHeadBorderWidth"),
    col.header.border.color = get0("formTableColHeadBorderColor"),
    col.header.fill = get0("formTableColHeadFill"),
    col.header.font.family = get0("formTableColHeadFontFamily"),
    col.header.font.color = get0("formTableColHeadFontColor"),
    col.header.font.size = get0("formTableColHeadFontSize"),
    col.header.font.weight = get0("formTableColHeadFontWeight"),
    col.header.font.style = get0("formTableColHeadFontStyle"),
    col.header.align.horizontal = get0("formTableColHeadAlignHoriz"),
    col.header.align.vertical = get0("formTableColHeadAlignVertical"),
    col.header.pad = get0("formTableColHeadPad", ifnotfound = 0),
    col.header.height = get0("formTableColHeadHeight"),
    show.row.headers = formRowShowHead,
    row.header.labels = get0("formTableRowLabels"),
    row.header.border.width = get0("formTableRowHeadBorderWidth"),
    row.header.border.color = get0("formTableRowHeadBorderColor"),
    row.header.fill = get0("formTableRowHeadFill"),
    row.header.font.family = get0("formTableRowHeadFontFamily"),
    row.header.font.color = get0("formTableRowHeadFontColor"),
    row.header.font.size = get0("formTableRowHeadFontSize"),
    row.header.font.weight = get0("formTableRowHeadFontWeight"),
    row.header.font.style = get0("formTableRowHeadFontStyle"),
    row.header.align.horizontal = get0("formTableRowHeadAlignHoriz"),
    row.header.align.vertical = get0("formTableRowHeadAlignVertical"),
    row.header.pad = get0("formTableRowHeadPad", ifnotfound = 0),
    corner = get0("formTableCornerLabels"),
    corner.fill = get0("formTableCornerFill"),
    corner.border.width = get0("formTableCornerBorderWidth"),
    corner.border.color = get0("formTableCornerBorderColor"),
    corner.font.family = get0("formTableCornerFontFamily"),
    corner.font.color = get0("formTableCornerFontColor"),
    corner.font.size = get0("formTableCornerFontSize"),
    corner.font.weight = get0("formTableCornerFontWeight"),
    corner.font.style = get0("formTableCornerFontStyle"),
    corner.align.horizontal = get0("formTableCornerAlignHoriz"),
    corner.align.vertical = get0("formTableCornerAlignVertical"),
    corner.pad = get0("formTableCornerPad", ifnotfound = 0),
    cell.prefix = get0("formTableCellPrefix"),
    cell.suffix = get0("formTableCellSuffix"),
    cell.fill = get0("formTableCellFill"),
    cell.border.width = get0("formTableCellBorderWidth"),
    cell.border.color = get0("formTableCellBorderColor"),
    cell.font.family = get0("formTableCellFontFamily"),
    cell.font.color = get0("formTableCellFontColor"),
    cell.font.size = get0("formTableCellFontSize"),
    cell.font.weight = get0("formTableCellFontWeight"),
    cell.font.style = get0("formTableCellFontStyle"),
    cell.align.horizontal = get0("formTableCellAlignHoriz"),
    cell.align.vertical = get0("formTableCellAlignVertical"),
    cell.pad = get0("formTableCellPad", ifnotfound = 0),
    use.predefined.css = FALSE) else CChart(chart.type = formChartType,
    x = pd$data,
    annotation.list = annot.list,
    overlay.annotation.list = overlay.annot.list,
    weights = pd$weights,
    multi.color.series = isTRUE(get0("formMultiColorSeries")),
    small.multiples = get0("formSmallMultiples", ifnotfound=FALSE),
    nrows = get0("formSmallMultNRows"),
    x.order = get0("formSmallMultXOrder"),
    average.show = get0("formSmallMultAverage", ifnotfound = FALSE),
    average.color = get0("formSmallMultAverageColor"),
    share.axes = get0("formSmallMultShareAxes", ifnotfound = TRUE),
    panel.title.show = get0("formSmallMultTitle", ifnotfound = TRUE),
    panel.title.font.family = get0("formSmallMultTitleFontFamily", ifnotfound = template$fonts$`Panel title`$family),
    panel.title.font.color = get0("formSmallMultTitleFontColor", ifnotfound = template$fonts$`Panel title`$color),
    panel.title.font.size = get0("formSmallMultTitleFontSize", ifnotfound = template$fonts$`Panel title`$size),
    panel.title.wrap = get0("formSmallMultTitleWrap"),
    panel.title.wrap.nchar = get0("formSmallMultTitleWrapNchar"),
    pad.left = get0("formSmallMultPadLeft", ifnotfound=0),
    pad.right = get0("formSmallMultPadRight", ifnotfound=0),
    pad.top = get0("formSmallMultPadTop"),
    pad.bottom = get0("formSmallMultPadBottom"),
    max.label.length = 0,
    #Scatter plot inputs.
    scatter.max.labels = get0("formScatterMaxLab", ifnotfound=20),
    scatter.labels.as.hovertext= if (exists("formScatterLabelType")) formScatterLabelType!="On chart" else TRUE,
    scatter.colors.as.categorical = if (exists("formScatterColorType")) formScatterColorType=="Categories" else TRUE,
    scatter.sizes.as.diameter = if (exists("formScatterSizeType")) formScatterSizeType=="Diameter" else FALSE,
    scatter.x.column = pd$scatter.variable.indices["x"],
    scatter.y.column = pd$scatter.variable.indices["y"],
    scatter.sizes.column = pd$scatter.variable.indices["sizes"],
    scatter.colors.column = pd$scatter.variable.indices["colors"],
    scatter.groups.column = pd$scatter.variable.indices["groups"],
    trend.lines = get0("formTrendLines", ifnotfound=FALSE),
    logos = get0("formLogos"),
    logo.size = get0("formLogoSize"),
    # Chart: DATA SERIES
    colors = colors.main,
    pie.subslice.colors = GetVectorOfColors(template, pd$data, QFilter,
        formChartType, palette = get0("formSubslicePalette"),
        palette.custom.color = get0("formSubsliceCustomColor"),
        palette.custom.gradient.start = get0("formSubsliceCustomGradientStart"),
        palette.custom.gradient.end = get0("formSubsliceCustomGradientEnd"),
        palette.custom.palette = get0("formSubsliceCustomPalette"),
        type = "Pie subslice"),
    x2.colors = if (is.null(pd2)) NULL else GetVectorOfColors(template, pd2$data,
        QFilter, "Line", palette = get0("formVal2Palette"),
        palette.custom.color = get0("formVal2CustomColor"),
        palette.custom.gradient.start = get0("formVal2CustomGradientStart"),
        palette.custom.gradient.end = get0("formVal2CustomGradientEnd"),
        palette.custom.palette = get0("formVal2CustomPalette")),
    # Secondary Y-axis (Column chart only)
    x2 = pd2$data,
    x2.line.type = get0("formVal2LineType"),
    x2.line.thickness = get0("formVal2LineThickness"),
    x2.shape = get0("formVal2LineShape"),
    x2.marker.show = get0("formVal2MarkerShow"),
    x2.marker.show.at.ends = get0("formVal2MarkerShowAtEnds", ifnotfound = FALSE),
    x2.marker.size = get0("formVal2MarkerSize"),
    x2.marker.symbols = get0("formVal2MarkerSymbols"),
    #Chart: FIT LINE
    fit.type = get0("formFit", ifnotfound="None"),
    fit.window.size = get0("formFitWindow", ifnotfound=2),
    fit.ignore.last = get0("formFitIgnoreLast"),
    fit.line.type = get0("formFitLineType"),
    fit.line.colors = GetVectorOfColors(template, pd$data, QFilter,
        formChartType, scatter.colors.column,
        get0("formMultiColorSeries", ifnotfound = FALSE),
        palette = get0("formFitPalette"),
        palette.custom.color = get0("formFitCustomColor"),
        palette.custom.gradient.start = get0("formFitCustomGradientStart"),
        palette.custom.gradient.end = get0("formFitCustomGradientEnd"),
        palette.custom.palette = get0("formFitCustomPalette")),
    fit.line.width = get0("formFitLineWidth", ifnotfound=1),
    fit.line.opacity = get0("formFitOpacity"),
    fit.CI.show = isTRUE(get0("formFitCI")),
    fit.CI.colors = GetVectorOfColors(template, pd$data, QFilter,
        formChartType, scatter.colors.column,
        get0("formMultiColorSeries", ifnotfound = FALSE),
        palette = get0("formFitCIPalette"),
        palette.custom.color = get0("formFitCICustomColor"),
        palette.custom.gradient.start = get0("formFitCICustomGradientStart"),
        palette.custom.gradient.end = get0("formFitCICustomGradientEnd"),
        palette.custom.palette = get0("formFitCICustomPalette")),
    fit.CI.opacity = get0("formFitCIOpacity"),
    # Chart: DATA LABELS
    data.label.show = get0("formDataLabelShow", ifnotfound = FALSE),
    data.label.show.at.ends = get0("formDataLabelShowAtEnds", 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=""),
    data.label.position = get0("formDataLabelPosition", ifnotfound="top middle"),
    data.label.align.horizontal = get0("formDataLabelHorizAlign", ifnotfound="Default"),
    label.auto.placement = get0("formScatterLabelAutoPlacement", ifnotfound = TRUE),
    x2.data.label.show = get0("formVal2DataLabelShow", ifnotfound = FALSE),
    x2.data.label.show.at.ends = get0("formVal2DataLabelShowAtEnds", ifnotfound = FALSE),
    x2.data.label.format = ChartNumberFormat(list(get0("formVal2DataLabelNumberType"), get0("formVal2DataLabelDateType"), get0("formVal2DataLabelNumberCustom"), get0("formVal2DataLabelSeparateThousands"), .ifNull(get0("formVal2DataLabelDecimals"), default.digits2)), !is.null(attr(pd2$data, "statistic")) && grepl("%", attr(pd2$data, "statistic"), fixed = TRUE)),
    x2.data.label.font.size = get0("formVal2DataLabelFontSize", ifnotfound = template$fonts$`Data labels`$size),
    x2.data.label.font.family = get0("formVal2DataLabelFontFamily", ifnotfound = template$fonts$`Data labels`$family),
    x2.data.label.font.color = get0("formVal2DataLabelFontColor", ifnotfound = template$fonts$`Data labels`$color),
    x2.data.label.font.autocolor = exists("formVal2DataLabelFontColorOptions") && formVal2DataLabelFontColorOptions == "Automatically",
    x2.data.label.prefix = get0("formVal2DataLabelPrefix", ifnotfound=""),
    x2.data.label.suffix = get0("formVal2DataLabelSuffix", ifnotfound=""),
    # Chart: FONT
    global.font.family = get0("formFont", ifnotfound = template$global.font$family),
    global.font.color = get0("formFontColor", ifnotfound = template$global.font$color),
    global.font.size = get0("formFontSize", ifnotfound = template$global.font$size),
    #Chart: GRIDLINES
    grid.show = get0("formShowGrid", ifnotfound=TRUE),
    values.line.width = get0("formValuesLineWidth", ifnotfound = 0),
    values.line.color = get0("formValuesLineColor", ifnotfound = default.grid.color),
    values.zero = isTRUE(get0("formValuesZeroLineWidth") > 0) ||
        formChartType %in% c("Area", "Bar", "Column", "Line", "Pyramid"),
    values.zero.line.width = get0("formValuesZeroLineWidth", ifnotfound = 0),
    values.zero.line.color = get0("formValuesZeroLineColor", ifnotfound = default.grid.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.grid.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.bubbles.show = get0("formLegendBubblesShow"),
    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"),
    legend.wrap = get0("formLegendWrap", ifnotfound = FALSE),
    legend.wrap.nchar = get0("formLegendWrapNchar"),
    # Chart: TITLE
    title = paste0("", if (sum(nchar(get0("formTitle"))) > 0) formTitle else pd$chart.title),
    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)),
    title.align = get0("formTitleHAlign", ifnotfound = "center"),
    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),
    subtitle.align = get0("formSubtitleHAlign", ifnotfound = "center"),
    footer = paste0("", if (sum(nchar(get0("formFooter"))) > 0) formFooter else pd$chart.footer),
    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.align = get0("formFooterHAlign", ifnotfound = "center"),
    footer.wrap = get0("formFooterWrap", ifnotfound=FALSE),
    footer.wrap.nchar = get0("formFooterWrapNchar"),
    #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.maxnum = get0("formCategoriesTickMaxNum"),
    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.mark.length = get0("formCategoriesTickLen", ifnotfound = 3),
    categories.tick.mark.color = get0("formCategoriesTickColor", ifnotfound = "transparent"),
    categories.tick.angle = if (!exists("formCategoriesTickAngle")) NULL else switch(formCategoriesTickAngle, Automatic=NULL, Horizontal=0, 'Vertical (clockwise)' = 90, Diagonal=45, 'Vertical (counter-clockwise)' = -90),
    categories.tick.label.wrap = get0("formLabelWrap", ifnotfound=FALSE),
    categories.tick.label.wrap.nchar = get0("formLabelWrapNchar", ifnotfound=100),
    categories.tick.align.horizontal = get0("formCategoriesTickHorizAlign", ifnotfound = "Default"),
    #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.mark.length = get0("formValuesTickLen", ifnotfound = 0),
    values.tick.mark.color = get0("formValuesTickColor", ifnotfound = "transparent"),
    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 = " ") else pd$values.title),
    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.tick.maxnum = get0("formValuesTickMaxNum"),
    values.number.ticks = get0("formValuesTickMaxNum"),
    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: Secondary Y axis
    y2.line.width = get0("formVal2LineWidth", ifnotfound = 0),
    y2.line.color = get0("formVal2LineColor", ifnotfound = default.grid.color),
    y2.bounds.minimum = get0("formVal2Min"),
    y2.bounds.maximum = get0("formVal2Max"),
    y2.tick.show = get0("formVal2AxisShow", ifnotfound = FALSE),
    y2.tick.mark.length = get0("formVal2TickLen", ifnotfound = 0),
    y2.tick.mark.color = get0("formVal2TickColor", ifnotfound = "transparent"),
    y2.tick.maxnum = get0("formVal2TickMaxNum"),
    y2.tick.format = ChartNumberFormat(list(get0("formVal2NumberType"), get0("formVal2DateType"), get0("formVal2NumberCustom"), get0("formVal2SeparateThousands"), .ifNull(get0("formVal2Decimals"), default.digits2)), !is.null(attr(pd2$data, "statistic")) && grepl("%", attr(pd2$data, "statistic"), fixed = TRUE)),
    y2.tick.prefix = paste0("", get0("formVal2Prefix"), get0("formVal2Currency")),
    y2.tick.suffix = get0("formVal2Suffix", ifnotfound=""),
    y2.title =  paste0("", if (sum(nchar(get0("formVal2Title", ifnotfound=" "))) > 0) get0("formVal2Title", ifnotfound = " ") else pd2$values.title),
    y2.title.font.family = get0("formVal2TitleFontFamily", ifnotfound = template$fonts$`Values axis title`$family),
    y2.title.font.color = get0("formVal2TitleFontColor", ifnotfound = template$fonts$`Values axis title`$color),
    y2.title.font.size = get0("formVal2TitleFontSize", ifnotfound = sum(template$fonts$`Values axis title`$size)),
    y2.tick.font.size = get0("formVal2TickFontSize", ifnotfound = sum(template$fonts$`Values axis tick labels`$size)),
    y2.tick.font.family = get0("formVal2TickFontFamily", ifnotfound = template$fonts$`Values axis tick labels`$family),
    y2.tick.font.color = get0("formVal2TickFontColor", 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),
    hovertext.bg.color = get0("formHoverBgColor"),
    hovertext.bg.autocolor = get0("formHoverBgAutoColor"),
    hovertext.bg.opacity = get0("formHoverBgOpacity"),
    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 = exists("formCustomMargin")),
    # Chart: APPEARANCE
    type = if(get0("formStackSeries", ifnotfound=FALSE)) "Stacked" else formChartType,
    bar.gap = get0("formBarGap"),
    bar.group.gap = get0("formBarGroupGap", ifnotfound = 0),
    adjust = get0("formBandwidth"),
    automatic.lower.density = get0("formAutomaticLower"),
    pie.inner.radius = get0("formPieRadius"),
    pie.border.color = get0("formBorderColor"),
    pie.data.threshold = get0("formHoverThreshold")/100,
    density.color = get0("formDensityColor"),
    vertical = get0("formVertical"),
    show.mean = get0("formShowMean"),
    show.median = get0("formShowMedian"),
    show.quartiles  = get0("formShowQuartiles"),
    show.range = get0("formShowRange"),
    show.values = get0("formShowValues", ifnotfound = FALSE),
    histogram.cumulative = get0("formHistogramCumulative"),
    histogram.counts = get0("formHistogramCounts"),
    maximum.bins = get0("formMaximumBins"),
    box.points = get0("formBoxPoints"),
    mean.color = get0("formMeanColor"),
    median.color = get0("formMedianColor"),
    quartile.color =  get0("formQuartilesColor"),
    range.color =  get0("formRangeColor"),
    values.color =  get0("formValuesColor"),
    window.start = get0("formWindowStart"),
    range.bars = get0("formRangeBars"),
    line.type = get0("formLineType"),
    line.thickness = get0("formLineThickness"),
    shape = get0("formLineShape"),
    opacity = get0("formFillOpacity"),
    marker.show = if(isTRUE(get0("formMarkerShow"))) TRUE else NULL,
    marker.show.at.ends = get0("formMarkerShowAtEnds", ifnotfound = FALSE),
    marker.symbols = get0("formMarkerSymbols"),
    marker.size = get0("formMarkerSize", ifnotfound = 6),
    marker.border.width = get0("formMarkerBorderWidth", ifnotfound = 1),
    marker.border.opacity = get0("formMarkerBorderOpacity"),
    marker.border.colors = get0("formMarkerBorderColor"),
    # BarPictograph parameters
    image = get0("formIcon"),
    custom.image = get0("formCustomIcon"),
    base.image = get0("formBaseImage", ifnotfound = ""),
    hide.base.image = get0("formHideBase", ifnotfound = FALSE),
    base.icon.color = get0("formBaseColor", ifnotfound = ""),
    scale = if (exists("formIconScale")) as.numeric(formIconScale),
    total.icons = if (exists("formTotalIcons")) as.numeric(formTotalIcons),
    icon.ncol = if (exists("formIconNCol")) as.numeric(formIconNCol),
    fix.icon.nrow = get0("formFixNRows", ifnotfound = TRUE),
    fill.direction = get0("formFillDirection"),
    label.color.asIcon = get0("formLabelColorAsIcon", ifnotfound = FALSE),
    categories.tick.align = get0("formCategoriesTickAlign"),
    pad.row = get0("formIconPadding", ifnotfound = 0),
    graphic.width.inch = QOutputSizeWidth,
    graphic.height.inch = QOutputSizeHeight,
    # GeographicMap parameters
    mapping.package = get0("formMapPackage"),
    high.resolution = get0("formHighRes", ifnotfound = TRUE),
    show.missing.regions = get0("formShowMissingRegions", ifnotfound = TRUE),
    treat.NA.as.0 = get0("formNAasZero", ifnotfound = FALSE),
    color.NA = get0("formNAColor"),
    ocean.color = get0("formOceanColor"),
    zip.country = get0("formZipCountry"),
    background = get0("formBackgroundMap"),
    # Heat parameters
    sort.rows = get0("formHeatSortRows"),
    sort.columns = get0("formHeatSortColumns"),
    standardization = get0("formHeatStandardization"),
    left.columns = get0("formLeftColumns"),
    left.column.headings = get0("formLeftColumnHeadings"),
    right.columns = get0("formRightColumns"),
    right.column.headings = get0("formRightColumnHeadings"),
    # General arguments
    signif.show = show.significance,
    font.units = get0("formFontUnits", ifnotfound = "pt"),
    background.fill.color = get0("formBgColor", ifnotfound = "transparent"),
    background.fill.opacity = get0("formBgOpacity", ifnotfound = 1.0),
    append.data = TRUE,
    warn.if.no.match = FALSE)
{
    "formChartType": "Area",
    "formStackSeries": false,
    "formSmallMultiples": false,
    "formAsPercentages": false,
    "formScatterLabelType": "As hover text"
}