Visualization - Streamgraph

From Q
Jump to: navigation, search

A Streamgraph is like a stacked Area Chart without the series based on a fixed x-axis. They are usually used to show changes in different categories over time through the use of flowing, stream-like shapes.

A Streamgraph is like a stacked Area chart without the series based on a fixed x-axis. They are usually used to show changes in different categories over time through the use of flowing, stream-like shapes.

Like with an area chart, the value is denoted by the colored region. The difference is that the value is represented by the area between the two lines, rather the area from a line to the axis.

Example

The example below uses data from a fast-food consumption survey. The following visualization shows the fast-food consumption habits of respondents over time, using the following table.

Brand tracking table.png

Create a Streamgraph in Displayr

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

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.

Chart

DATA SERIES
Color palette The colors used for the streams.
X AXIS
Tick units The units of the x-axis. If Automatic the units will be set according to the data.
Automatic tick interval If set, the tick interval is determined automatically from the data.
Axis tick interval The number of Tick units between each tick label. The value may be rounded to produce more balanced tick labels.
Number type The formatting of the x-axis tick labels.
Y AXIS
Number type The formatting of the y-axis tick labels.
Maximum number of ticks The maximum number of tick labels to show on the y-axis.
Note that if Tick units are Month or Day (or are determined to be as such automatically) then ticks are shown at the start of every year or month respectively and in addition to the chosen tick interval.

More Information

How to create a streamgraph in Displayr

Acknowledgements

The stream chart is produced using the R streamgraph package.

Code

var chart_type_default = "Stream";
var default_aggregation = false; // when data is Variables, required even if FirstAggregate is not a control so that Table created from a chart has the same data as shown by the chart
var default_smallmultiples = false;

// VERSION 5.0.0
var allow_control_groups = Q.fileFormatVersion() > 10.9; // Group controls for Displayr and later versions of Q
var controls = []; // All controls displayed must be inserted here
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",
                               alternatives: ["Table", 
                                              "Area", 
                                              "Bar",
                                              "Bar Pictograph",
                                              "Bean", 
                                              "Box",
                                              "Column",
                                              "Density",
                                              "Donut",
                                              "Geographic Map",
                                              "Heat", 
                                              "Histogram", 
                                              "Line",
                                              "Palm", 
                                              "Pie",
                                              "Pyramid",  
                                              "Radar", 
                                              "Stream", 
                                              "Scatter",
                                              "Time Series",
                                              "Venn", 
                                              "Violin"], default_value: chart_type_default, required: true});
var chartType = qChartType.getValue();
controls.push(qChartType);

// Some constants and helper functions
var displayr = Q.isOnTheWeb();
function isEmpty(x) { return (x == undefined || x.getValue() == null && (x.getValues() == null || x.getValues().length == 0)) }
function isBlankSheet(x) { return (x.getValue() == null || x.getValue().length == 0) }
function isEmptyString(x) { return (x == undefined || x == null || x == "") }
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]); }

// 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 " + (displayr ? "'Insert > Utilities > Visualization > Create Template'" : "'Automate > Browse Online Library > Visualization > Create Template'")
var format_prompt = "Format automatically determined based on input data. Click on up/down buttons to adjust";
categories_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
values_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
hover_number_formats = ["Automatic", "Number", "Category", "Percentage", "Metric units suffix", "Scientific", "Custom"]; // no currency since cannot add as prefix and no date because of charting requirements
data_label_formats = ["Automatic", "Number", "Percentage"];
date_formats = ["YY (Year, 2 digit)", "DD Mon YY", "DD Month YY", "DD MM YY", "YYYY (Year, 4 digit)", "DD Mon YYYY", "DD Month YYYY", "DD MM YYYY", "Mon DD YY", "Month DD YY", "MM DD YY", "Mon DD YYYY", "Month DD YYYY", "MM DD YYYY", "YY Mon DD", "YY Month DD", "YY MM DD", "YYYY Mon DD", "YYYY Month DD", "YYYY MM DD", "Custom"];
var marker_symbols = ["circle", "circle-open", "square", "square-open", "diamond", "diamond-open"];
 
// * Font alternatives *
font_families = ["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", "Default colors", "Colorblind safe colors", "Rainbow", "Light pastels", "Strong colors", "Spectral colors (red, yellow, blue)", "Spectral colors (blue, yellow, red)", "Reds, dark to light", "Reds, light to dark", "Greens, dark to light", "Greens, light to dark", "Blues, dark to light", "Blues, light to dark", "Greys, dark to light", "Greys, light to dark", "Heat colors (yellow, red)", "Terrain colors (green, beige, grey)", "Custom color", "Custom gradient", "Custom palette"];
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 = displayr ? "Variables in 'Data'" : "Variables";
yLabel = 'Groups';
INPUTS_data = ['pasted', 'table', 'r', 'variables'];
variables_types = ['variables'];
CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesAxisShow', 'CategoriesTickFont', 'CategoriesTickAngle', 
                   'CategoriesNumberFormat', 'LabelWrap','CategoriesPrefix','CategoriesGrid','CategoriesLine'];
VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat',
               'ValuesGrid','ValuesLine'];
HOVER = ['HoverNumberFormat', 'HoverFontFamily', 'HoverFontSize'];
LEGEND = ['LegendShow', 'LegendFont', 'LegendXPos', 'LegendYPos', 'LegendOrientation'];
INPUTS = ['x', 'y', "AsPercentages", "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');
    MARGINS = ['CustomMargin'];
    DATA_SERIES.push('FillOpacity');
    DATA_SERIES.push('LineThickness');
    DATA_SERIES.push('LineMarkers');
    HOVER.push('HoverShow');
} else if (chartType == "Palm")
{
    CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesTickFont'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 'ValuesSuffix', 'ValuesPrefix'];
    INPUTS = ['x', "AsPercentages", "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")
{
    INPUTS_data = ['pasted', 'table', 'variables'];
    INPUTS = ['x', 'y'];
    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 = null;
    HOVER = ['HoverFontFamily', 'HoverFontSize'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 'ValuesSuffix', 'ValuesPrefix', 'ValuesMin', 'ValuesMax'];
    CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesAxisShow', 'CategoriesTickFont', 'CategoriesTickAngle', 
                   'CategoriesNumberFormat', 'LabelWrap','CategoriesPrefix', 'CategoriesMin', 'CategoriesMax'];
    APPEARANCE = ['SmallMultiples', 'ScatterTrendLine']; 
    LEGEND.push('LegendShowAuto');
    DATA_LABELS = null;   // this is overridden if LabeledScatter
    MARGINS = ['CustomMargin'];
    var isLabeled = false;
    //isLabeled is set in the DATA MANIPULATION tab, as is DATA_LABELS and APPEARANCE

}  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");
    LEGEND.push('LegendShowAuto');
    DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont', 'DataLabelFontMulticolor', 'DataLabelPrefix', 'DataLabelSuffix'];
    if (chartType == "Column" || chartType == "Bar")
        ANNOTATIONS = ["", "Arrow - up", "Arrow - down", "Border", "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", "Hide", "Shadow", "Text - after data label", "Text - before data label"];
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 'ValuesSuffix', 'ValuesPrefix', 'ValuesMin', 'ValuesMax', 'ValuesGrid', 'ValuesLine'];



    //    DATA_LABELS.push("DataLabelAnnot");
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 'ValuesSuffix', 'ValuesPrefix', 'ValuesMin', 'ValuesMax', 'ValuesGrid', 'ValuesLine'];
    CATEGORIES_AXIS = ['CategoriesTitle', 'CategoriesAxisShow', 'CategoriesTickFont', 'CategoriesTickAngle', 'CategoriesNumberFormat', 'LabelWrap','CategoriesPrefix', 'CategoriesMin', 'CategoriesMax', 'CategoriesGrid', 'CategoriesAxis', 'CategoriesGrid', 'CategoriesLine'];
    HOVER.push('HoverShow');

}  else if (["Box", "Bean", "Density", "Histogram", "Violin"]. indexOf(chartType) != -1)
{    
    INPUTS_data = ['pasted', 'table', 'r', 'variables', 'sets'];
    INPUTS = ['x', 'y'];
    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'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesAxisShow', 'ValuesTickFont', 'ValuesNumberFormat', 'ValuesMin', 'ValuesMax', 'ValuesGrid', 'ValuesLine'];
    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');
    HOVER.push('HoverShow');

} else if (chartType == "Pyramid")
{
    SwappedXY();
    APPEARANCE = ['SmallMultiples', 'BarGap'];
    VALUES_AXIS = ['ValuesTitle', 'ValuesMax'];
    DATA_SERIES.push("FillOpacity");
    FITLINE = null;
    LEGEND = null;
    DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont', 'DataLabelFontMulticolor', 'DataLabelPrefix', 'DataLabelSuffix'];
    MARGINS = ['CustomMargin'];
    HOVER.push('HoverShow');

} else if (chartType == "Bar Pictograph")
{
    SwappedXY();
    INPUTS = ['x', 'AsPercentages', '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 = "Arial";
var globalFontColor = "#2C2C2C";
var globalFontSize = 7.5;

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", default_value: default_smallmultiples, 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", default_value: false, prompt: "Stack bars on top of each other to compare cumulative values."});
        var isStacked = qStack.getValue();
        controls.push(qStack);
    }
}

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

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

        }
        controls.push(form.numericUpDown({name: "form" + name + "FontSize", label: prefix + " font size", default_value: fontSize, increment: 0.5}));
    }
}


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

colorPaletteControls = function(suffix, labelSuffix, palettes, controls, addColorValues = false)
{
        var qColor = form.comboBox({name: "form" + suffix + "Palette", label: "Color palette" + labelSuffix, alternatives: palettes, default_value: palettes[0], required: true});
        controls.push(qColor);
        var colOpt = qColor.getValue();
        if (colOpt == "Custom color")
        {
            controls.push(form.colorPicker({name: "form" + suffix + "CustomColor", label: "Custom color" + labelSuffix, default_value: default_colors[0]}));
        }
        if (colOpt == "Custom gradient")
        {
            controls.push(form.colorPicker({name: "form" + suffix + "CustomGradientStart", label: "Gradient start" + labelSuffix, default_value: "#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", "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: ["Table", "RItem"], 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}));
        controls.push(form.numericUpDown({name: "formTable" + name + "FontSize", label: "Font size", 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, 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)
                controls.push(form.textBox({name: "formAnnotData" + aii, label: "Data" + astr, required: true, 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"], 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. Use '<br>' to add line break."}));
                controls.push(form.textBox({name: "formAnnotSuffix" + aii, label: "Suffix" + astr, required: false, prompt: "Optional text to append to text annotation. Use '<br>' to add line break."}));
                controls.push(form.comboBox({name: "formAnnotFontFamily" + aii, label: "Font family" + astr, alternatives: font_families, default_value: globalFontFamily}));
                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++;
    }
}


 
/* 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 += " in 'Pages'";
   outputPrompt += " in the 'Pages' tree (top-left)";
}
var tableInput = form.dropBox({name: "formTable", label: outputLabel, types: ['Table', "RItem:!StandardChart!SignicanceTest:!KMeans:!phraseList:!tidyText:!wordBag"], required: false, multi: chartType == "Scatter" && !show_as_small_mult, prompt: outputPrompt});

// Input as variables in the Data tab
var variables_controls = []; 
var variables_prompt = displayr ? "Variables are shown in the 'Data' tree (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:['variable: Categorical'], prompt: variables_prompt, multi: false, required: true, default_value: VColors.getValue()})
        variables_controls.push(VGroups);
    }
}

if (show_as_small_mult && chartType == "Radar")
{
    CATEGORIES_AXIS = null;
    VALUES_AXIS = ['ValuesMin','ValuesMax'];
}

// Pasted input data
var pastedInput = form.dataEntry({label: "Paste or type data", name: "formPastedData", prompt: "Opens a spreadsheet into which you can enter data."});

// Hide other data sources if one of them is already selected
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)
    {
        var qTidy = form.checkBox({label: "Automatically tidy the data", name: "formTidy", default_value: ["Table", "Bar", "Column", "Line", "Scatter"].indexOf(chartType) == -1, prompt: "Convert to numeric and simplify structure."});
        tidy = qTidy.getValue();
        controls.push(qTidy);
    }
    if (aggregate && allow_control_groups && (dtype == 'table' || dtype == 'pasted'))
    {
         var qGroupLast = 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"});
         controls.push(qGroupLast);
    }
    if (aggregate && (!allow_control_groups || (dtype == 'variables' && n_variables == 2 && n_yvariables == 0)))
    { 
         var qGroupLast = 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"});
         controls.push(qGroupLast);
    }
    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;
            }
            var qColNames = form.checkBox({label: "First row contains column names", name: "formPastedColumnNames", default_value: true});
            var qRowNames = form.checkBox({label: rowname_label, name: "formPastedRowNames", prompt: rowname_prompt, default_value: rowname_default});
            controls.push(qColNames);
            controls.push(qRowNames);
        }
    }
    qTidyLabel = form.checkBox({name: "formTidyLabels", label: "Tidy labels", default_value: chart_type_default != "Table", prompt: "Extract common prefixes 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('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 manipuations 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);
        }
        // Set default formatting if 'as percentage' is checked
        if (asPct)
        {
            var formats = [values_number_formats, hover_number_formats, data_label_formats];
            for (var i = 0; i < formats.length; i++)
            {
                var format_array = formats[i];
                var pct_idx = format_array.indexOf('Percentage');
                if (pct_idx != -1) 
                {
                    format_array.splice(pct_idx, 1);
                    format_array.splice(0, 0, "Percentage");
                    var auto_idx = format_array.indexOf('Automatic');
                    if (auto_idx != -1)
                        format_array.splice(auto_idx, 1);
                }
            }
        }
        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);
        }
        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"]
            var qSelectRowsOpt = form.comboBox({label: "Select rows to show by", name: "formSelectRowsOpt", alternatives: rowOpts, default_value: rowOpts[0]});
            controls.push(qSelectRowsOpt);
        }

        var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl", types: ["Control: listbox,combobox"], 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 (!isEmpty(qSelectRowsCtrl) || 
            (displayr && 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);
        }
        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"]
            var qSelectColsOpt = form.comboBox({label: "Select columns to show by", name: "formSelectColsOpt", alternatives: colOpts, default_value: colOpts[0]});
            controls.push(qSelectColsOpt);
        }

        var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl", types: ["Control: listbox,combobox"], 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 (!isEmpty(qSelectColsCtrl) || 
            (displayr && 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!SignicanceTest:!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});

    // 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: 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)
    {
        var qTidy = form.checkBox({label: "Automatically tidy the data", name: "formTidy2", default_value: chartType != "Table" && chartType != "Scatter", prompt: "Convert to numeric and simplify structure."});
        tidy = qTidy.getValue();
        controls.push(qTidy);
    }
    if (aggregate && allow_control_groups && (dtype2 == 'table' || dtype2 == 'pasted'))
    {
         var qGroupLast = 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"});
         controls.push(qGroupLast);
    }
    if (aggregate && (!allow_control_groups || (dtype2 == 'variables' && n_variables == 2 && n_yvariables == 0)))
    { 
         var qGroupLast = 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"});
         controls.push(qGroupLast);
    }
    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;
            }
            var qColNames = form.checkBox({label: "First row contains column names", name: "formPastedColumnNames2", default_value: true});
            var qRowNames = form.checkBox({label: rowname_label, name: "formPastedRowNames2", prompt: rowname_prompt, default_value: rowname_default});
            controls.push(qColNames);
            controls.push(qRowNames);
        }
    }
    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")
    {
        qTranspose = form.checkBox({name: "formTranspose2", 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('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 manipuations 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"]
            var qSelectRowsOpt = form.comboBox({label: "Select rows to show by", name: "formSelectRowsOpt2", alternatives: rowOpts, default_value: rowOpts[0]});
            controls.push(qSelectRowsOpt);
        }

        var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl2", types: ["Control: listbox,combobox"], 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"]
            var qSelectColsOpt = form.comboBox({label: "Select columns to show by", name: "formSelectColsOpt2", alternatives: colOpts, default_value: colOpts[0]});
            controls.push(qSelectColsOpt);
        }

        var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl2", types: ["Control: listbox,combobox"], 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", default_value: 0}));

        if (allow_control_groups)
            form.group('Font');
        var qGlobalFontFamily = form.comboBox({name: "formFont", label: "Global font family", alternatives: font_families, default_value: "Arial"});
        controls.push(qGlobalFontFamily);
        globalFontFamily = qGlobalFontFamily.getValue();
    
        var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: !allow_control_groups ? "" : "Global font color", default_value: "#2C2C2C"});
        controls.push(qGlobalFontColor);
        globalFontColor = qGlobalFontColor.getValue();
    
        var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 10, 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});
    var qTemplate = form.dropBox({name: "formTemplate", label: "Use template", types: ["RItem:AppearanceTemplate"], required: false, prompt: template_prompt});
    controls.push(qTemplate);
    use_default_fonts = !isEmpty(qTemplate);
}

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: true, 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 != "Pyramid")
            {
                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)
        {
            APPEARANCE.push('ScatterLogos');
            data_label_formats = ["Automatic", "Number", "Category", "Percentage", "Currency"];
            LEGEND = ['LegendShowAuto', 'LegendShow', 'LegendFont', 'LegendBubbles'];
            DATA_LABELS = ['DataLabelFont', 'DataLabelFontMulticolor', 'DataLabelAutoPosition', 'ScatterMaxLab'];
            GRIDLINES = ['ShowGrid'];
            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 (!isLabeled)
        {
            FITLINE = ['FitLine'];
            GRIDLINES = null;
            CATEGORIES_AXIS.push('CategoriesGrid');
            CATEGORIES_AXIS.push('CategoriesLine');
            VALUES_AXIS.push('ValuesGrid');
            VALUES_AXIS.push('ValuesLine');
        }
            
    }
    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 && isLabeled)
    {
        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)
    {
        var qBarGap = form.numericUpDown({name: "formBarGap", label: "Gap between bars", default_value: 0.15, minimum: 0.0, maximum: 1.0, increment: 0.05, prompt: "Control spacing between bars"});
        controls.push(qBarGap);
    }
}

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 && !show_second_yaxis && (chartType == "Bar" || chartType == "Column"))
        {
            var qMultColor = form.checkBox({name: "formMultiColorSeries", label: "Multiple colors within a single series", default_value: false});
            controls.push(qMultColor);
            if (qMultColor.getValue())
                FITLINE = null;
        }
        //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 && chartType != "Bar")
        {
            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: "Arial"});
            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: "#2C2C2C"});
            controls.push(qGlobalFontColor);
            globalFontColor = qGlobalFontColor.getValue();
        }
        if (FONT.indexOf('GlobalFontSize') > -1)
        {
            var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 7.5, 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);
        }
    }
}
 
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});
            controls.push(qDatalabFont);
        }

        if (DATA_LABELS.indexOf('DataLabelFontMulticolor') > -1)
        {
            var hasAutocolor = isStacked || chartType == "Line" || chartType == "Pyramid" || 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: 7.5, 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 && dtype == "table" && ANNOTATIONS != null) 
    {
        annotControls(controls, ANNOTATIONS);
    }

    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, 7.5, 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", "Border", "Hide", "Marker border", "Shadow", "Text - after data label", "Text - before data label"], 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)
    if (hasleg && LEGEND.indexOf('LegendOrientation') > -1)
    {
        controls.push(form.comboBox({name: "formLegendOrientation", label: "Legend orientation", alternatives: ['Vertical', 'Horizontal'], default_value: chartType == 'Time Series' ? 'Horizontal' : 'Vertical'}));
    }

    var legendCustomPos = false;
    if (hasleg && (LEGEND.indexOf('LegendXPos') > -1 || LEGEND.indexOf('LegendYPos') > -1))
    {
        var qLegCustom = form.checkBox({name: "formLegendCustom", label: "Customize legend position", default_value: false});
        controls.push(qLegCustom);
        legendCustomPos = qLegCustom.getValue();
    }
    if (hasleg && legendCustomPos && LEGEND.indexOf('LegendXPos') > -1)
    {
        if (chartType == "Time Series")
            var qLegX = form.numericUpDown({name: "formLegendXPos", label: "Horizontal placement", default_value: 0.5, increment: 0.02, minimum: 0.1, maximum: 0.9, prompt: "Choose whether legend should go across the whole chart (0.1) or just the right-most portion (0.9)"});
        else
            var qLegX = form.numericUpDown({name: "formLegendXPos", label: "Horizontal placement", default_value: 1.02, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (far left) to 3 (far right)"});
        controls.push(qLegX);
    }
    if (hasleg && legendCustomPos && LEGEND.indexOf('LegendYPos') > -1)
    {
        var qLegY = form.numericUpDown({name: "formLegendYPos", label: "Vertical placement", default_value: 1.00, 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);
    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.5, "", true, true, 20, false);
    }
    if (TITLE.indexOf('Subtitle') > -1)
        formattedTextControls("Subtitle", "Subtitle", controls, 9, "")
    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);
}

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();
    
    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") ? 1 : 0});
        controls.push(qXGridWidth);
        if (qXGridWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formCategoriesGridColor", label: "Grid line color", default_value: "#E1E1E1"}));
    } 
    if (CATEGORIES_AXIS.indexOf('CategoriesLine') > -1)
    {
        var qXLineWidth = form.numericUpDown({name: "formCategoriesLineWidth", label: "Axis line width", minimum: 0, default_value: 0});
        controls.push(qXLineWidth);
        if (qXLineWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formCategoriesLineColor", label: "Axis line color", default_value: "#000000"}));
    }
    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, 9, "Leave blank to read axis title from input data", true)
    }
    if (chartType == "Bar Pictograph")
        labelShowX = "Show bar labels";
    
    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, 7.5, 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"})); 
        }
        if (CATEGORIES_AXIS.indexOf('CategoriesTickAngle') > -1)
            controls.push(form.comboBox({name: "formCategoriesTickAngle", label: axisPrefix + " label orientation", alternatives: ["Automatic", "Horizontal", "Vertical", "Diagonal"], default_value: "Automatic"}));
        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: 5, increment: 5, maximum: 1000}));
        }    
    }
}
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();

    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: 1});
        controls.push(qYGridWidth);
        var showYgrid = qYGridWidth.getValue() > 0;
        if (showYgrid)
            controls.push(form.colorPicker({name: "formValuesGridColor", label: "Grid line color", default_value: "#E1E1E1"}));
    } 
    if (VALUES_AXIS.indexOf('ValuesLine') > -1)
    {
        var qYLineWidth = form.numericUpDown({name: "formValuesLineWidth", label: "Axis line width", minimum: 0, default_value: 0});
        controls.push(qYLineWidth);
        if (qYLineWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formValuesLineColor", label: "Axis line color", default_value: "#000000"}));
    }
    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, 9, "Leave blank to read axis title from input data", true);
    }

    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 (showY && VALUES_AXIS.indexOf('ValuesNumberTicks') > -1) //Weird parameter for Stream Graph
            controls.push(form.numericUpDown({name: "formValuesNumberTicks", label: "Maximum number of ticks", default_value: 5, increment: 1, minimum: 1, maximum: 1000}));
        if (showY && VALUES_AXIS.indexOf('ValuesTickFont') > -1)
            fontControls("ValuesTick", axisPrefix + " label", controls);
    }
}

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 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, 9, "Leave blank to read axis title from input data", true);

    var qShowY2 = form.checkBox({name: "formVal2AxisShow", label: "Show axis", default_value: true});
    controls.push(qShowY2);
    if (qShowY2.getValue()) 
    {
        numberFormatControls("Val2", axisPrefix, controls, values_number_formats);
        fontControls("Val2Tick", axisPrefix + " tick label", controls);
    }
}

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});
        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: 8.5}); 
        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 5.0.0
library(flipFormat)
library(flipChart)
library(flipChartBasics)
template <- get0("formTemplate")

# 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 <- "Default colors"

    template <- list(colors = default.palette, global.font = list(family = "Arial", color = "#2C2C2C", 
        size = 7.5, units = "pt"), fonts = list(`Data labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), Legend = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), Title = list(
        family = "Arial", color = "#2C2C2C", size = 12), Subtitle = list(
        family = "Arial", color = "#2C2C2C", size = 9), Footer = list(
        family = "Arial", color = "#2C2C2C", size = 6), `Categories axis title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Categories axis tick labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), `Values axis title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Values axis tick labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), `Panel title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Hover text` = list(
        family = "Arial", color = "#2C2C2C", size = 8.625)), colors = "Default colors")
}

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 = get0("formGroups"), 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")) formSelectRowsCtrl else get0("formSelectRows")
select.cols <- if (exists("formSelectColsCtrl")) 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")), 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), 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"))
       
# 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))
    }
}
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))

pn <- PrepareNumbers(categories.format.list = list(get0("formCategoriesNumberType"), get0("formCategoriesDateType"), get0("formCategoriesNumberCustom"), get0("formCategoriesSeparateThousands"), get0("formCategoriesDecimals")), values.format.list = list(get0("formValuesNumberType"), get0("formValuesDateType"), get0("formValuesNumberCustom"), get0("formValuesSeparateThousands"), get0("formValuesDecimals")), hover.format.list = list(get0("formHoverNumberType"), get0("formHoverDateType"), get0("formHoverNumberCustom"), get0("formHoverSeparateThousands"), get0("formHoverDecimals")), data.labels.format.list = list(get0("formDataLabelNumberType"), get0("formDataLabelDateType"), get0("formDataLabelCustom"), get0("formDataLabelSeprateThousands"), get0("formDataLabelDecimals")), !is.null(attr(pd$data, "statistic")) && grepl("%", attr(pd$data, "statistic"), fixed = TRUE))

# Data for secondary axis - only used for Column charts
pd2 <- NULL
if (!is.null(get0("formTable2")) || !is.null(get0("formPastedData2")) || !is.null(get0("formX2")))
{
    select.rows2 <- if (exists("formSelectRowsCtrl2")) formSelectRowsCtrl2 else get0("formSelectRows2")
    select.cols2 <- if (exists("formSelectColsCtrl2")) 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), 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"))
}

# Collect list of annotations which may vary in length and composition
annot.list <- NULL
a.ind <- 1
if (formChartType %in% c("Bar", "Column", "Line", "Scatter"))
{
    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
    }
}
    
# Creating the chart or table
# Strange line breaks is necessary for the R output to be named according to the R code
chart <- 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 = formTableDecimals,
    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,
    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"), get0("formVal2DataLabelDecimals")), !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 = "#000000"),
    values.grid.width = get0("formValuesGridWidth", ifnotfound = 0),
    values.grid.color = get0("formValuesGridColor", ifnotfound = "#E1E1E1"),
    categories.line.width = get0("formCategoriesLineWidth", ifnotfound = 0),
    categories.line.color = get0("formCategoriesLineColor", ifnotfound = "#000000"),
    categories.grid.width = get0("formCategoriesGridWidth", ifnotfound = 0),
    categories.grid.color = get0("formCategoriesGridColor", ifnotfound = "#E1E1E1"),
    # Chart: LEGEND
    legend.show = get0("formLegendShow", ifnotfound = FALSE),
    legend.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"),
    # 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)),
    subtitle = get0("formSubtitle", ifnotfound=""),
    subtitle.font.family = get0("formSubtitleFontFamily", ifnotfound = template$fonts$Subtitle$family),
    subtitle.font.color = get0("formSubtitleFontColor", ifnotfound = template$fonts$Subtitle$color),
    subtitle.font.size = get0("formSubtitleFontSize", ifnotfound = template$fonts$Subtitle$size),
    footer = paste0("", if (sum(nchar(get0("formFooter"))) > 0) formFooter else 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.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.format = pn$categories.number.format,
    categories.tick.prefix = paste0("", get0("formCategoriesPrefix"), get0("formCategoriesCurrency")),    # currency is just another prefix
    categories.tick.suffix = get0("formCategoriesSuffix", ifnotfound=""),
    categories.tick.interval = get0("formCategoriesTickInterval", ifnotfound=0),
    categories.tick.units = get0("formCategoriesTickUnits"),
    categories.title = paste0("", if (sum(nchar(get0("formCategoriesTitle", ifnotfound = " "))) > 0) get0("formCategoriesTitle", ifnotfound = " ") else pd$categories.title),
    categories.title.font.family = get0("formCategoriesTitleFontFamily", ifnotfound = template$fonts$`Categories axis title`$family),
    categories.title.font.color = get0("formCategoriesTitleFontColor", ifnotfound = template$fonts$`Categories axis title`$color),
    categories.title.font.size = get0("formCategoriesTitleFontSize", ifnotfound = sum(template$fonts$`Categories axis title`$size)), 
    categories.tick.font.family = get0("formCategoriesTickFontFamily", ifnotfound = template$fonts$`Categories axis tick labels`$family),
    categories.tick.font.color = get0("formCategoriesTickFontColor", ifnotfound = template$fonts$`Categories axis tick labels`$color),
    categories.tick.font.size = get0("formCategoriesTickFontSize", ifnotfound = sum(template$fonts$`Categories axis tick labels`$size)),
    categories.tick.angle = if (!exists("formCategoriesTickAngle")) NULL else switch(get0("formCategoriesTickAngle"), Automatic=NULL, Horizontal=0, Vertical=90, Diagonal=45),
    categories.tick.label.wrap = get0("formLabelWrap", ifnotfound=FALSE),
    categories.tick.label.wrap.nchar = get0("formLabelWrapNchar", ifnotfound=100),
    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.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.number.ticks = get0("formValuesNumberTicks"),
    values.tick.font.size = get0("formValuesTickFontSize", ifnotfound = sum(template$fonts$`Values axis tick labels`$size)),
    values.tick.font.family = get0("formValuesTickFontFamily", ifnotfound = template$fonts$`Values axis tick labels`$family),
    values.tick.font.color = get0("formValuesTickFontColor", ifnotfound = template$fonts$`Values axis tick labels`$color),
    #Chart: Secondary Y axis
    y2.line.width = get0("formVal2LineWidth", ifnotfound = 0),
    y2.line.color = get0("formVal2LineColor", ifnotfound = "#000000"),
    y2.bounds.minimum = get0("formVal2Min"), 
    y2.bounds.maximum = get0("formVal2Max"), 
    y2.tick.show = get0("formVal2AxisShow", ifnotfound=FALSE),
    y2.tick.format = ChartNumberFormat(list(get0("formVal2NumberType"), get0("formVal2DateType"), get0("formVal2NumberCustom"), get0("formVal2SeparateThousands"), get0("formVal2Decimals")), !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 = TRUE),
    # Chart: APPEARANCE
    type = if(get0("formStackSeries", ifnotfound=FALSE)) "Stacked" else formChartType,
    bar.gap = get0("formBarGap"),
    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),
    # 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
    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)