Visualization - Scatter - Scatter

From Q
Jump to navigation Jump to search
VizIcon Scatterplot.svg

A Scatterplot uses dots to represent values for two different numeric variables. The position of each dot indicates values for an individual data point along the axes. Dots can also be color-coded and sized based on other variables.

Examples

The following example shows the relationship between the price of a diamond and its mass (carat). The quality of the cut is color-coded and the depth of the diamond is represented by the size of the dot. Scatterplots are most useful when they have continuous numeric variables in the x-axis and y-axis.

Scatter plot object inspector.png

Create a Scatterplot in Displayr

1. Go to Insert > Visualization > Scatterplot
2. Under Inputs > DATA SOURCE > X coordinates, select the variable you wish to be displayed horizontally
3. Under Inputs > DATA SOURCE > Y coordinates, select the variables you wish to be displayed vertically
4. [OPTIONAL] Under Inputs > DATA SOURCE > Sizes and Colors, select variables to alter the sizes and colors of the dots.

More Examples

More examples of Scatterplots with different types of input can be found here

Object Inspector Options

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

Inputs

DATA SOURCE
Scatterplots accepts tables supplied using either Paste or type data or existing output in 'Pages'. These are expected to be tables where each row of the input data is shown as a separate point. The first two columns control the x and y coordinates, respectively. If the provided, the values of the third column controls the sizes, and the fourth column controls the colors of the points. Additional columns in the table can be referred to for use with annotations. When the input table contains rownames, these will be used as the data labels. If multiple tables are selected, each one is expected to be in the same format as described above, but row names and column names must be the same across all tables. Note that the default format of the input data for Scatter plots is different from other visualizations and Row/Column manipulations may not behave as expected. In these cases, you may want to select Input data contains y-values in multiple columns.
Alternatively, the user can assign X coordinates, Y coordinates, Sizes and Colors to be variables or outputs. This option is more flexible because each of these 4 components can be separately assigned instead of being extracted from the same table. However, it is also more complicated because the behaviour may change slightly depending on the inputs chosen.
  • Inputs are variables. This is the simplest use case; a marker is shown for each entry in the variables (i.e the variables are expected to be the same length).
  • Inputs are tables. In this case, if the tables are simple 1-column tables, then they will behave exactly the same as the variable. However, where they have additional attributes, the chart will attempt to use these as well. If the tables have row labels, these will be used as the labels to the data points. It is also possible to explicitly use the row labels as X coordinates by selecting Use category labels instead of values. In the case where this is selected and a banner is used, the span labels are used instead of the row labels. If the Y coordinates is a 2-dimensional table, then the columns will be treated as separate data series (i.e. in different colors). If the Y coordinates table contains multiple statistics, then these may be used in the annotations.
  • X or Y coordinates are a Standard R Regression model output. In this case either the regression coefficients or the importance scores are used as the data input. This is useful in particular for creating Quad Maps from a Driver Analysis output.
Input data contains y-values in multiple columns. When this is selected, each cell in the input table is shown as a separate point. The values in the table are used as the y-coordinates, whereas the x-coordinates is taken from the row labels. Each column is shown as a separate group, with the colors of the groups controlled by the color palette (under Data series). All points will be shown with the same size. If the table contains multiple statistics, these can be used to add annotations to the chart.

Chart

APPEARANCE
Show labels on chart or as hovertext. The second option (default) handles large datasets better, and offers more charting options. However, for small datasets where the first option performs better at showing a moderate number of labels on the chart. The labeled scatterplot will automatically position the labels to avoid overlap, but users can also drag on labels to move them and click on markers to hide/show labels. These changes are remembered on recalculation, but will be reset when the input data changes.
DATA LABELS
Automatically position data labels When labels are shown on chart, the data labels are sometimes automatically positioned to the side of the actual point for better visibility. Unchecking this will place the data label on top of the data point.
Maximum number of labels to plot. This option limits the number of labels shown when the labels are shown on chart. It is useful when there are many points with overlapping labels. The remaining points will be shown without labels.
Treat sizes variable as area or diameter. If the input data contains a sizes variable, the points will be shown with either the area or the diameter of the points proportional to the absolute value of the size variable (e.g. Example 3). The sizes variable can either be specified as a variable in theSizes dropdown option , or as the third numeric column of a linked output or pasted table. The variable used for the Sizes will be coerced into a numeric vector (which may not make sense for character or categorical variables).
Treat colors variable as categories or numeric scale. If the input data contains a colors variables, the color of each point will be determined by the value of the corresponding entry in the colors variable. The colors can be shown as categories (e.g. Example 1), or vary continuously over a numeric scale (e.g. Example 3). The colors variable can either be specified as a variable in the Colors dropdown option, or as the fourth (numeric) column of a linked output or pasted table.
Show lines with arrows If multiple tables are given, then lines are shown between corresponding points in the different tables, with an arrow pointing towards the point in the last table. The of the tables order determines the order of lines between points. Otherwise, trend lines are added between consecutive points in the same group (Treat colors variable must be set to Categories). If lines with arrows are used then Sizes are ignored (because no markers are drawn).
Logos A comma separated list of URLs to be used instead of labels.
Logo size The size of the logos, specified in increments of 0.01.
Marker size If the input data does not contain a sizes variable, the marker size specifies the marker diameter in pixels. If a sizes variable is provided, marker size is used to scale the marker size. Specifically, the largest marker will have a diameter of 50/6 * marker size. (For the default value of 6, the largest circle will be approximately half an inch in diameter).
ANNOTATION
Annotation The type of annotation to add, this can be one of Arrow - up, Arrow - down, Marker border, Text - after data label, Text - before data label and Border, Hide, Shadow. The last three are only visible if text is added first. Annotations can be added whether labels are show on chart are as hovertext. However, they will be automatically positioned and draggable if labels are shown on chart.
Data If the input data is variables from a dataset, then the data used for the annotation can be a variable selected using a combobox. If the input data is a table, then type in the column name of the data to use.
Threshold Enter a threshold value to select values to annotate. If data is numeric, setting the threshold to -Inf or Inf will select all (non-missing) values. For date variables, the threshold will try to parsed as a date string. For an ordered categorical variable, type in the name of one of the levels.
QUADRANTS
Show quadrants Whether or not to show midpoint lines, dividing the scatterplot into quadrants. When this checkbox is ticked, additional controls to customize the midpoint lines, quadrant background and quadrant title will be made available.
Set midpoint on X/Y axis using one of Average, Median, Value, Calculation. If Average or Median is chosen, then the midpoint line will calculated by applying the function to all the X/Y coordinates in the data set; otherwise the user can place the midpoint lines at a specific numeric value, or refer to a separate (or inline) calculation.
Midpoint X/Y line width Width of the midpoint line in pixels.
Midpoint X/Y line color Color of the mdidpoint line.
Midpoint X/Y line type One of Solid, Dash, Dot.
TOP/BOTTOM LEFT/RIGHT QUADRANT
Quadrant background color Color of one of four quadrants on the scatterplot.
Quadrant title Optional text to label each of the quadrants. By default these are positioned at the four corners of the scatterplot, but they can be dragged around and will retain the position.
PLOT AREA
Show border When this option is selected, then a border will drawn around all 4 edges of the scatterplot. This will replace the x-axis line and the y-axis line which only covers the bottom and left edge.
Plot border color Color of the border line. This will replace both the color of x-axis line and y-axis line.
Plot border width Width of the border line in pixels. This will replace both the width of both x-axis line and y-axis line.
Fix aspect ratio Set aspect ratio of the scatterplot to 1.
LEGEND
Legend title Optional text shown above the legend. This either shows the color categories, when the color variable is categoric, or a color scale bar when the color variable is numeric.
Show bubble legend (if applicable) Show legend associating the size of the points with the values in the sizes variable. Note that the bubble legend is only shown when a sizes variable is used.
Bubble legend title Optional text shown above the bubble legend.

More Information

What is a scatterplot
What is a labeled scatterplot
Using scatterplots to chart trends
Adding logos to scatterplots in Displayr


Code

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

// Some constants and helper functions
function isEmpty(x) { return (x == undefined || x == null || x.getValue() == null && (x.getValues() == null || x.getValues().length == 0)) }
function isBlankSheet(x) { return (x.getValue() == null || x.getValue().length == 0) }
function isEmptyString(x) { return (x == undefined || x == null || x == "") }
function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; }
function rgbToHex(x) { return "#" + componentToHex(x[0]) + componentToHex(x[1]) + componentToHex(x[2]); }

const DEFAULT_FONT_COLOR = "#444444";
const DEFAULT_FONT_FAMILY = "Open Sans";
const DEFAULT_GRID_COLOR = "#D2D8E1";
const DEFAULT_GRID_WIDTH = 0.5;
const DEFAULT_AXIS_COLOR = "#1C283B";
const DEFAULT_AXIS_WIDTH = 1;

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

// * Combo box alternatives *
var template_prompt = "Create a template to control color palettes and fonts for all visualizations in the document using 'Visualization > Template'";
var format_prompt = "Format automatically determined based on input data. Click on up/down buttons to adjust";
categories_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
values_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
hover_number_formats = ["Automatic", "Number", "Category", "Percentage", "Metric units suffix", "Scientific", "Custom"]; // no currency since cannot add as prefix and no date because of charting requirements
data_label_formats = ["Automatic", "Number", "Percentage"];
date_formats = ["YY (Year, 2 digit)", "DD Mon YY", "DD Month YY", "DD MM YY", "YYYY (Year, 4 digit)", "DD Mon YYYY", "DD Month YYYY", "DD MM YYYY", "Mon DD YY", "Month DD YY", "MM DD YY", "Mon DD YYYY", "Month DD YYYY", "MM DD YYYY", "YY Mon DD", "YY Month DD", "YY MM DD", "YYYY Mon DD", "YYYY Month DD", "YYYY MM DD", "Custom"];
var marker_symbols = ["circle", "circle-open", "square", "square-open", "diamond", "diamond-open"];
 
// * Font alternatives *
font_families = !!Q.GetAvailableFontNames ? Q.GetAvailableFontNames() : ["Arial", "Arial Black", "Comic Sans MS",  "Courier New", "Georgia", "Impact", 
                 "Open Sans", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana"];

// * Palette alternatives *
palettes = ["Default or template settings", "Office colors", "Colorblind safe colors", "Rainbow", "Light pastels", "Strong colors", "Spectral colors (red, yellow, blue)", "Spectral colors (blue, yellow, red)", "Reds, dark to light", "Reds, light to dark", "Greens, dark to light", "Greens, light to dark", "Blues, dark to light", "Blues, light to dark", "Greys, dark to light", "Greys, light to dark", "Heat colors (yellow, red)", "Terrain colors (green, beige, grey)", "Custom color", "Custom gradient", "Custom palette"];
gradual_palettes = ["Default or template settings", "Blues, light to dark", "Blues, dark to light", "Greys, light to dark", "Greys, dark to light", "Reds, light to dark", "Reds, dark to light", "Greens, light to dark", "Greens, dark to light", "Spectral colors (red, yellow, blue)", "Spectral colors (blue, yellow, red)","Heat colors (yellow, red)", "Terrain colors (green, beige, grey)", "Custom gradient", "Custom palette"];
line_subslice_palletes = ["Group colors"].concat(palettes);
default_colors = ["#3e7dcc", "#04b5ac", "#f5c524", "#c44e41", "#8cc0ff", "#ff905a", "#345e8c", "#04827b", "#967f47", "#96362f", "#2c4374", "#4d525a"];

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

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

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

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

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

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

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

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

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


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

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

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

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

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


formattedTextControls = function(name, prefix, controls, fontSize, promptText, 
                                 alwaysFormat = false, wrap = false, wrapDefault = 20, textbox = true,
                                 horizAlign = false)
{
    if (textbox)
    {
        var qText = form.textBox({name: "form" + name, label: prefix, required: false, prompt: promptText});
        var textOpt = qText.getValue();
        controls.push(qText);
    }
    if (alwaysFormat || !textbox || !isEmptyString(textOpt))
    {
        var qTextFontDefault = form.checkBox({name: "form" + name + "FontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qTextFontDefault);
        if (!qTextFontDefault.getValue())
        {
            controls.push(form.comboBox({name: "form" + name + "FontFamily", label: prefix + " font family", alternatives: font_families, default_value: globalFontFamily, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
            controls.push(form.colorPicker({name: "form" + name + "FontColor", label: prefix + " font color", default_value: globalFontColor}));
            controls.push(form.numericUpDown({name:"form" + name + "FontSize", label: prefix + " font size", default_value: fontSize, increment: 0.5}));
        }
        if (wrap)
        {
            var qTextWrap = form.checkBox({name: "form" + name + "Wrap", label: "Wrap " + prefix, default_value: true});
            controls.push(qTextWrap);
            if (qTextWrap.getValue())
            {
                var qTextWrapN = form.numericUpDown({name: "form" + name + "WrapNchar", label: prefix + " width (in characters)", default_value: wrapDefault, minimum: 5, increment: 5, maximum: 5000});
                controls.push(qTextWrapN);
            }
        }
    }
    if (horizAlign)
        controls.push(form.comboBox({name: "form" + name + "HAlign", label: "Alignment", alternatives: ["Left", "Center", "Right"], default_value: "Center"}));
}

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

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

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


    if (formatFont)
    {
        controls.push(form.colorPicker({name: "formTable" + name + "FontColor", label: "Font color", default_value: globalFontColor}));
        controls.push(form.comboBox({name: "formTable" + name + "FontFamily", label: "Font family", alternatives: font_families, default_value: globalFontFamily, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."}));
        controls.push(form.numericUpDown({name: "formTable" + name + "FontSize", label: "Font size", increment: 0.5, default_value: globalFontSize}));
        controls.push(form.comboBox({name: "formTable" + name + "FontWeight", label: "Font weight", alternatives: ["Normal", "Bold"], default_value: defaultWeight}));
        controls.push(form.comboBox({name: "formTable" + name + "FontStyle", label: "Font style", alternatives: ["Normal", "Italic"], default_value: "Normal"}));
        var qHorizAlign = form.comboBox({name: "formTable" + name + "AlignHoriz", label: "Horizontal alignment", alternatives: ["Left", "Center", "Right"], default_value: defaultHAlign});
        controls.push(qHorizAlign);
        if (qHorizAlign.getValue() != "Center")
            controls.push(form.numericUpDown({name: "formTable" + name + "Pad", label: "Padding", default_value: 0, prompt: "Space between cell border and text in pixels"}));
        controls.push(form.comboBox({name: "formTable" + name + "AlignVertical", label: "Vertical alignment", alternatives: ["Top", "Middle", "Bottom"], default_value: "Middle"}));
    }
}

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

    // Creating other input controls on the Inputs page.
    var asPct = false;
    if (INPUTS != null)
    {
        if (INPUTS.indexOf('HidePercentSymbol') > -1)
            controls.push(form.checkBox({name: "formHidePercent", label: "Hide percent symbol", prompt: "Percentage data will be treated as if it was just numeric data", default_value: false}));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    }
}


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

        if (allow_control_groups)
            form.group('Font');
        var qGlobalFontFamily = form.comboBox({name: "formFont", label: "Global font family", alternatives: font_families, default_value: DEFAULT_FONT_FAMILY, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
        controls.push(qGlobalFontFamily);
        globalFontFamily = qGlobalFontFamily.getValue();
    
        var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: !allow_control_groups ? "" : "Global font color", default_value: DEFAULT_FONT_COLOR});
        controls.push(qGlobalFontColor);
        globalFontColor = qGlobalFontColor.getValue();
    
        var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 8, increment: 0.5});
        controls.push(qGlobalFontSize);
        globalFontSize = qGlobalFontSize.getValue();
    
        var qFontUnits = form.comboBox({name: "formFontUnits", label: "Font units", alternatives: ["pt", "px"], default_value: "pt", prompt: "Are font sizes specified in terms of points or pixels?"});
        controls.push(qFontUnits);

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

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

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

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

    if (APPEARANCE.indexOf('SmallMultiples') > -1 && dtype != 'tables')
    {
        if (qSmallMult.getValue())
        {
            var qSmallMultNRow = form.numericUpDown({label: "Number of rows of panels", name: "formSmallMultNRows", default_value: 2, minimum: 1, prompt: "Control layout of small multiples"});
            controls.push(qSmallMultNRow);
            if (chartType != "Geographic Map" && chartType != "Scatter")
                LEGEND = null; 
            if (chartType != "Geographic Map" && chartType != "Scatter" && chartType != "Funnel")
            {
                var qSmallMultAverage = form.checkBox({label: "Show average of all series", name: "formSmallMultAverage", default_value: false, prompt: "In each panel, show average of combined data for comparison"});
                controls.push(qSmallMultAverage);
                if (qSmallMultAverage.getValue())
                {
                    var qSmallMultAverageColor = form.colorPicker({name: "formSmallMultAverageColor", label: !allow_control_groups ? "" : "Color of average series", default_value: "#999999"});
                    controls.push(qSmallMultAverageColor);
                }
            }
            if (chartType != "Radar" && chartType != "Geographic Map")
            {
                var qSmallMultShareAxes = form.checkBox({label: "Share axes between panels", name: "formSmallMultShareAxes", default_value: true, prompt: "Hold range of axes constant and only show axes on outer panels"});
                controls.push(qSmallMultShareAxes);
            }
            controls.push(form.textBox({label: "Order of panels", name: "formSmallMultXOrder", required: false, prompt: "e.g '2,1,3', or leave blank to follow order of input data"}));

            if (chartType == "Scatter")
            {
                controls.push(form.numericUpDown({label: "Panel horizontal padding", name: "formSmallMultXGap", default_value: 0.2, minimum: 0.00, maximum: 1.00, increment: 0.01}));
                controls.push(form.numericUpDown({label: "Panel vertical padding", name: "formSmallMultYGap", default_value: 0.3, minimum: 0.00, maximum: 1.00, increment: 0.01}));

            } else
            {
                controls.push(form.numericUpDown({label: "Panel 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: "Panel 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: "Panel top padding", name: "formSmallMultPadTop", default_value: 0.1, minimum: 0.00, maximum: 1.00, increment: 0.01}));
                controls.push(form.numericUpDown({label: "Panel bottom padding", name: "formSmallMultPadBottom", default_value: 0.01, minimum: 0.00, maximum: 1.00, increment: 0.01}));
            }
        }
    }
    if (chartType == 'Scatter')
    {
        var knownLabel = (dtype == 'variables' && !isEmpty(VLabels)) || dtype  == 'tables';
        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 (APPEARANCE.indexOf("ScatterLogos") > -1 && !show_as_small_mult)
    {
        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.01}));
    }
    if (APPEARANCE.indexOf("ScatterTrendLine") > -1 && !show_as_small_mult)
    {
        var prompt_text = "Add lines between corresponding points in successive tables";
        if (dtype != 'tables')
            prompt_text = "Add lines between points in the same group in the order supplied in the data source";
        var scatterTrendLines = form.checkBox({label: "Show lines with arrows", name: "formTrendLines", default_value: false, prompt: prompt_text});
        controls.push(scatterTrendLines);
    }
    if (APPEARANCE.indexOf("ScatterSizeType") > -1)
    {
        controls.push(form.comboBox({label: "Treat sizes variable as", name:"formScatterSizeType", alternatives:['Area', 'Diameter'], default_value:'Area'}));
    }
    if (APPEARANCE.indexOf("ScatterColorType") > -1)
    {
        controls.push(form.comboBox({label: "Treat colors variable as", name:"formScatterColorType", alternatives:['Categories', 'Numeric scale'], default_value:'Categories', prompt: "Treat values in the color variables as separate categories (may be slow if there are many categories); or as values on a linear scale"}));
    }


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

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

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

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

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

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

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

            }
        }

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

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

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

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

            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.01, prompt: "Control transparency of confidence interval ribbon"});
                controls.push(qFitCIOpacity);
            }
        }
    }
}


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

    if (!use_default_fonts)
    {
        if (FONT.indexOf('GlobalFontFamily') > -1)
        {
            var qGlobalFontFamily = form.comboBox({name: "formFont", label: "Global font family", alternatives: font_families, default_value: DEFAULT_FONT_FAMILY, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
            controls.push(qGlobalFontFamily);
            globalFontFamily = qGlobalFontFamily.getValue();
        }
        if (FONT.indexOf('GlobalFontColor') > -1 && !(chartType == "Bar Pictograph" && labColAsIcon))
        {
            if (!allow_control_groups)
            {
                var qGlobalFontColorLabel = form.newLabel("Global font color");
                controls.push(qGlobalFontColorLabel);
            }
            var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: !allow_control_groups ? "" : "Global font color", default_value: DEFAULT_FONT_COLOR});
            controls.push(qGlobalFontColor);
            globalFontColor = qGlobalFontColor.getValue();
        }
        if (FONT.indexOf('GlobalFontSize') > -1)
        {
            var qGlobalFontSize = form.numericUpDown({name: "formFontSize", label: "Font size", default_value: 8, increment: 0.5});
            controls.push(qGlobalFontSize);
            globalFontSize = qGlobalFontSize.getValue();
        }
        if (FONT.indexOf('FontUnits') > -1)
        {
            var qFontUnits = form.comboBox({name: "formFontUnits", label: "Font units", alternatives: ["pt", "px"], default_value: "pt", prompt: "Are font sizes specified in terms of points or pixels?"});
            controls.push(qFontUnits);
        }
    }
}

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

if (DATA_LABELS != null)
{
    if (allow_control_groups)
        form.group("DATA LABELS");
    var datalabshow = true;
    if (DATA_LABELS.indexOf('DataLabelPosition') > -1)
    {
        var qLabelPos = form.comboBox({name:"formDataLabelPosition", label: "Show data label", alternatives: ["No", "Next to bar", "Below row label", "Above row label", "Below icons", "Above icons"], default_value: "Next to bar"});
        controls.push(qLabelPos);
        var dLabOpt = qLabelPos.getValue();
        if (dLabOpt == "No")
            datalabshow = false;
        if (dLabOpt == "Below icons" || dLabOpt == "Above icons")
        {
            var qLabelHAlign = form.comboBox({name:"formDataLabelHorizAlign", label:" Data label horizontal alignment", alternatives: ["Default", "Left", "Center", "Right"], default_value: "Default"});
            controls.push(qLabelHAlign);
        }
    }
    if (DATA_LABELS.indexOf('DataLabelShow') > -1)
    {
        var qDatalabShow = form.checkBox({name: "formDataLabelShow", label: "Show data labels", default_value: false, prompt: "Show values of data points on the chart"});
        controls.push(qDatalabShow);
        var datalabshow = qDatalabShow.getValue();
        if (datalabshow && isStacked)
        {
            if (chartType == "Bar" || chartType == "Column")
                controls.push(form.numericUpDown({name: "formDataLabelThreshold", label: "Minimum threshold to show data labels", minimum: 0.0, maximum: 1.0, default_value: 0.05, increment: 0.01, prompt: "Data labels will be hidden for values which are smaller than this proportion of the total range"}));

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

    }
    if (!datalabshow && APPEARANCE.indexOf("AdditionalColumns") > -1 && (!isEmpty(qLeftCol)|| !isEmpty(qRightCol)))
    {
        // Allow left and right columns in Heatmaps to be formatted even if datalabels not shown
        DATA_LABELS = ['DataLabelShow', 'DataLabelFormat', 'DataLabelFont'];
        datalabshow = true; 
    }
    if (datalabshow && DATA_LABELS.indexOf('DataLabelFont') > -1)
    {
        var qDatalabFontDefault = form.checkBox({name: "formDataLabelFontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qDatalabFontDefault);
        var datalabFontDefault = qDatalabFontDefault.getValue(); 
        
        if (!datalabFontDefault)
        {
            qDatalabFont = form.comboBox({name: "formDataLabelFontFamily", label: "Data label font family", alternatives: font_families, default_value: globalFontFamily, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
            controls.push(qDatalabFont);
        }

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

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


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

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

        var qLegY = form.numericUpDown({name: "formLegendYPos", label: "Vertical placement", default_value: ypos, increment: 0.01, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (below) and 3 (above)"});
        controls.push(qLegY);
    }
    if (LEGEND.indexOf('LegendBubbles') > -1)
    {
        var show_bubble_legend = form.checkBox({name: "formLegendBubblesShow", label: "Show bubble legend (if applicable)", default_value: true});
        controls.push(show_bubble_legend);
        var show_bubble_legend_title = form.checkBox({name: "formLegendBubblesTitleShow", label: "Show bubble legend title", default_value: true});
        controls.push(show_bubble_legend_title);
        if (show_bubble_legend_title.getValue())
            controls.push(form.textBox({label: "Bubble legend title", type: "text", name: "formLegendBubbleTitle", required: false}));

        if (show_bubble_legend.getValue())
        {
            fontControls("LegendBubble", "Bubble legend", controls)
            fontControls("LegendBubbleTitle", "Bubble legend title", controls)
        }
    }
}
if (TITLE != null)
{
    if (allow_control_groups)
        form.group('Title');
    if (TITLE.indexOf('Title') > -1)
        formattedTextControls("Title", "Title", controls, 12, "", true, false, 20, true, TITLE.indexOf('HAlign') > -1);
    if (show_as_small_mult)
    {
        var qSmallMultTitle = form.checkBox({name:"formSmallMultTitle", label:"Show panel titles", default_value: true, prompt: "Show series or group name above each panel"});
        controls.push(qSmallMultTitle);
        if (qSmallMultTitle.getValue())
            formattedTextControls("SmallMultTitle", "Panel title", controls, 10, "", true, true, 20, false, false);
    }
    if (TITLE.indexOf('Subtitle') > -1)
        formattedTextControls("Subtitle", "Subtitle", controls, 8, "", true, false, 20, true, TITLE.indexOf('HAlign') > -1);
    if (TITLE.indexOf('Footer') > -1)
        formattedTextControls("Footer", "Footer", controls, 6, "Optional footer. Leave blank to show default text", true, ["Heat", "Pie", "Donut"].indexOf(chartType) == -1, 100, true, TITLE.indexOf('HAlign') > -1);
}

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

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

    if (CATEGORIES_AXIS.indexOf('CategoriesMin') > -1)
    {
        controls.push(form.textBox({name: "formCategoriesMin", label: "Minimum value", required: false, prompt: "Leave blank to determine automatically from input data. Otherwise, enter a numeric value (no prefix or suffix) for a numeric axis; a date-string for a date axis; or a 0-based index for a categorical axis."}));
    }
    if (CATEGORIES_AXIS.indexOf('CategoriesMax') > -1)
    {
        controls.push(form.textBox({name: "formCategoriesMax", label: "Maximum value", required: false, prompt: "Leave blank to determine automatically from input data. Otherwise, enter a numeric value (no prefix or suffix) for a numeric axis; a date-string for a date axis; or a 0-based index for a categorical axis."}));
    }
    if (CATEGORIES_AXIS.indexOf('CategoriesGrid') > -1)
    {
        var qXGridWidth = form.numericUpDown({name: "formCategoriesGridWidth", label: "Grid line width", minimum: 0, default_value: (chartType == "Radar" || chartType == "Scatter") ? DEFAULT_GRID_WIDTH : 0, increment: 0.5, prompt: "Set to zero to hide line"});
        controls.push(qXGridWidth);
        if (qXGridWidth.getValue() > 0)
        {
            controls.push(form.colorPicker({name: "formCategoriesGridColor", label: "Grid line color", default_value: DEFAULT_GRID_COLOR}));
            controls.push(form.comboBox({name: "formCategoriesGridType", label: "Grid line type", alternatives: ["Solid", "Dot", "Dash"], default_value: "Solid"}));
        }

    } 
    if (CATEGORIES_AXIS.indexOf('CategoriesLine') > -1)
    {
        var qXLineWidth = form.numericUpDown({name: "formCategoriesLineWidth", label: "Axis line width", minimum: 0, default_value: 0, increment: 0.5, prompt: "Set to zero to hide line"});
        controls.push(qXLineWidth);
        if (qXLineWidth.getValue() > 0)
            controls.push(form.colorPicker({name: "formCategoriesLineColor", label: "Axis line color", default_value: DEFAULT_AXIS_COLOR}));
    }
    if (CATEGORIES_AXIS.indexOf('CategoriesZeroLine') > -1)
    {
        var qX0LineWidth = form.numericUpDown({name: "formCategoriesZeroLineWidth", label: "Zero line width", minimum: 0, default_value: 0, increment: 0.5});
        controls.push(qX0LineWidth);
        if (qX0LineWidth.getValue() > 0)
        {
            controls.push(form.colorPicker({name: "formCategoriesZeroLineColor", label: "Zero line color", default_value: DEFAULT_AXIS_COLOR}));
            controls.push(form.comboBox({name: "formCategoriesZeroLineType", label: "Zero line type", alternatives: ["Solid", "Dot", "Dash"], default_value: "Solid"}));
        }
    }
    
    var showX = true;
    if (CATEGORIES_AXIS.indexOf('CategoriesAxisShow') > -1)
    {
        qShowX = form.checkBox({name: "formCategoriesAxisShow", label: labelShowX, default_value: true});
        showX = qShowX.getValue();
        controls.push(qShowX);
    }

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

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

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

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

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

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

}

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

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

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


}

if (HOVER != null)
{
    if (allow_control_groups)
        form.group("HOVER");
    var showHover = true;
    if (HOVER.indexOf('HoverShow') > -1)
    {
        var qShowHover = form.checkBox({name: "formHoverShow", label: "Show hover text", default_value: true});
        showHover = qShowHover.getValue();
        controls.push(qShowHover);
    }
    if (showHover && HOVER.indexOf('HoverThreshold') > -1)
    {
        controls.push(form.numericUpDown({name: "formHoverThreshold", label: "Threshold to show hover instead of labels (%)", default_value: 0.3, minimum: 0.0, maximum: 100, increment: 0.1, prompt: "Data labels will be hidden and shown as hover text instead in segments which are smaller than the threshold"}));
    }
    if (showHover && HOVER.indexOf('HoverNumberFormat') > -1 )
    {
        var hover_default_format = (typeof dataLabelFormat != 'undefined') ? dataLabelFormat : hover_number_formats[0];
        if (chartType == "Time Series")
            hover_default_format = yNumberType;
        var qHoverNumberType = form.comboBox({name: "formHoverNumberType", label: "Hovertext number type", alternatives: hover_number_formats, default_value: hover_default_format, required: true});
        var hoverNumberType = qHoverNumberType.getValue();
        controls.push(qHoverNumberType);
        if (hoverNumberType == "Date/Time")
        {
            var qHoverDate = form.comboBox({name: "formHoverDateType", label: "Date type", alternatives: date_formats, default_value: date_formats[0], required: true});
            hoverNumberType = qHoverDate.getValue();
            controls.push(qHoverDate);
        }
        var qHoverCustom = null;
        if (hoverNumberType == "Custom")
            qHoverCustom = form.textBox({name: "formHoverNumberCustom", label: "Custom code", default_value: "%d %b %y", required: true, prompt: "D3 formatting code, e.g. ,.2%"});
        else if (hoverNumberType == 'Currency')
            qHoverCustom = form.textBox({name: "formHoverCurrency", label: "Currency symbol", default_value: "$", required: true}); 
        else if (hoverNumberType == 'Number' && chartType != "Venn" && chartType != "Stream")
            qHoverCustom = form.checkBox({name: "formHoverSeparateThousands", label: "Separate thousands by comma", default_value: true});
        if (qHoverCustom != null)
            controls.push(qHoverCustom);
        var qHoverDecimals = form.numericUpDown({name:"formHoverDecimals", label: hoverNumberType == "Metric unit suffix" ? "Significant digits": "Decimal places", minimum: hoverNumberType == "Metric unit suffix" ? 1 : 0, required: false});
        controls.push(qHoverDecimals);
    }
    if (showHover && HOVER.indexOf('HoverPrefixSuffix') > -1 )
    {
        var qHoverPrefix = form.textBox({name:'formHoverValuesPrefix', label: 'Custom prefix', required: false, prompt: "Optional text to prepend to hovertext values"});
        controls.push(qHoverPrefix);
        var qHoverSuffix = form.textBox({name:'formHoverValuesSuffix', label: 'Custom suffix', required: false, prompt: "Optional text to append to hovertext values"});
        controls.push(qHoverSuffix);
    }
    if (showHover && HOVER.indexOf('HoverFontFamily') > -1)
    {
        var qHoverFontDefault = form.checkBox({name: "formHoverFontDefault", label: "Use default or template fonts", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qHoverFontDefault);
        var hoverFontDefault = qHoverFontDefault.getValue(); 
    }
    if (showHover && !hoverFontDefault && HOVER.indexOf('HoverFontFamily') > -1)
    {
        var qHoverFontFamily = form.comboBox({name: "formHoverFontFamily", label: "Hovertext font family", alternatives: font_families, default_value: globalFontFamily, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
        controls.push(qHoverFontFamily);
    }
    if (showHover && (!hoverFontDefault || chartType == "Pie" || chartType == "Donut") && HOVER.indexOf('HoverFontColor') > -1)
    {
        var is_pie_chart  = chartType == "Pie" || chartType == "Donut";
        if (is_pie_chart)
        {
            var qHoverFontAutoColor = form.checkBox({name: "formHoverAutoFontColor", label: "Automatically determine font color", default_value: true, prompt: "Use black or white text depending on the hover background color"});
            controls.push(qHoverFontAutoColor);
        }
        if (!is_pie_chart || !qHoverFontAutoColor.getValue())
        {
            var qHoverFontColor = form.colorPicker({name: "formHoverFontColor", label: "Hovertext font color", default_value: chartType == "Pie" || chartType == "Donut" ? "#EFEFEF" : globalFontColor});
            controls.push(qHoverFontColor);
        }
    }
    if (showHover && !hoverFontDefault && HOVER.indexOf('HoverFontSize') > -1)
    {
        var qHoverFontSize = form.numericUpDown({name: "formHoverFontSize", label: "Hovertext font size", default_value: 9}); 
        controls.push(qHoverFontSize);
    }
    if (HOVER.indexOf('HoverBgColor') > -1)
    {
        var qHoverBgAutoColor = form.checkBox({name: "formHoverBgAutoColor", label: "Automatically set background color", default_value: true});
        controls.push(qHoverBgAutoColor);
        if (!qHoverBgAutoColor.getValue())
            controls.push(form.colorPicker({name: "formHoverBgColor", label: "Background color", default_value: "#CCCCCC"}));
        controls.push(form.numericUpDown({name: "formHoverBgOpacity", label: "Opacity", default_value: 0.8, maximum: 1.0, increment: 0.01}));
    }

}
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.01, default_value: 1.0}));
}

form.setInputControls(controls);
# VERSION 6.2.1
library(flipFormat)
library(flipChart)
library(flipChartBasics)
template <- get0("formTemplate")

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

# Set defaults if no template
if (is.null(template))
{
    if (formChartType == "Geographic Map" && isTRUE(get0("formBackgroundMap")))
        default.palette <- "Reds, light to dark"
    else if (formChartType %in% c("Geographic Map", "Heat"))
        default.palette <- "Blues, light to dark"
    else
        default.palette <- QSettings$ColorPalette

    default.font <- QAppearance$Font$Family
    default.color <- "#444444"
    template <- list(colors = default.palette, global.font = list(family = default.font, color = default.color,
        size = 8, units = "pt"), fonts = list(`Data labels` = list(
        family = default.font, color = default.color, size = 8), Legend = list(
        family = default.font, color = default.color, size = 8), Title = list(
        family = default.font, color = default.color, size = 12), Subtitle = list(
        family = default.font, color = default.color, size = 8), Footer = list(
        family = default.font, color = default.color, size = 8), `Categories axis title` = list(
        family = default.font, color = default.color, size = 10), `Categories axis tick labels` = list(
        family = default.font, color = default.color, size = 9), `Values axis title` = list(
        family = default.font, color = default.color, size = 10), `Values axis tick labels` = list(
        family = default.font, color = default.color, size = 9), `Panel title` = list(
        family = default.font, color = default.color, size = 10), `Hover text` = list(
        family = default.font, color = default.color, size = 9)))
}
default.grid.color = "#D2D8E1"

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

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

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

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

# Determine default number of decimals to show
number.type <- "None"
if (!is.null(attr(pd$data, "statistic")))
{
    number.type <- attr(pd$data, "statistic")
} else if (all(c("questions", "name") %in% names(attributes(pd$data))))
{
    dn <- dimnames(pd$data)
    number.type <- dn[[length(dn)]][1]
}
if (isTRUE(grepl("%", number.type)))
    number.type <- "Percent"
default.digits <- QAppearance$Number$DecimalPlaces[[number.type]]
if (is.null(default.digits))
    default.digits <- QAppearance$Number$DecimalPlaces[["None"]]
.ifNull <- function(a, b) if (is.null(a)) return(b) else return(a)

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

custom.palette <- get0("formCustomPalette")
if (exists("formPalette"))
{
    if (formPalette == "Custom palette (R output)")
    {
        custom.palette <- get0("formCustomPaletteROutput")
    }
    if (formPalette == "Custom palette (color pickers)")
    {
        custom.palette <- rep(NA, 12)
        for (i in 1:12)
            custom.palette[i] <- get0(paste0("formCustomPalette", i))
    }
}

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

data.statistic <- ""
if (!is.null(attr(pd$data, "statistic"))) {
    data.statistic <- attr(pd$data, "statistic")
} else if (is.array(pd$data) && !is.null(attr(pd$data, "questions"))) {
    dn <- dim(pd$data)
    data.statistic <- dimnames(pd$data)[[length(dn)]][1]
}
data.is.percentage <- grepl("%", data.statistic, fixed = TRUE)

pn <- PrepareNumbers(categories.format.list = list(get0("formCategoriesNumberType"), get0("formCategoriesDateType"), get0("formCategoriesNumberCustom"), get0("formCategoriesSeparateThousands"), get0("formCategoriesDecimals")), values.format.list = list(get0("formValuesNumberType"), get0("formValuesDateType"), get0("formValuesNumberCustom"), get0("formValuesSeparateThousands"), get0("formValuesDecimals")), hover.format.list = list(get0("formHoverNumberType"), get0("formHoverDateType"), get0("formHoverNumberCustom"), get0("formHoverSeparateThousands"), .ifNull(get0("formHoverDecimals"), default.digits)), data.labels.format.list = list(get0("formDataLabelNumberType"), get0("formDataLabelDateType"), get0("formDataLabelCustom"), get0("formDataLabelSeparateThousands"), .ifNull(get0("formDataLabelDecimals"), default.digits)), data.is.percentage)
if (formChartType == "Scatter")
{
    pn$categories.number.format <- flipChartBasics::ChartNumberFormat(list(get0("formCategoriesNumberType"), get0("formCategoriesDateType"), get0("formCategoriesNumberCustom"), get0("formCategoriesSeparateThousands"), get0("formCategoriesDecimals")), data.is.percentage)
    formChartType <- "CombinedScatter"
}

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

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

}

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

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

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