Visualization - Sparkline - Curve

From Q
Jump to: navigation, search

VizIcon Sparkline Curve.svg

A sparkline of a smoothed line chart. A sparkline is a small chart with axes and coordinates excluded. They are commonly used alongside more detailed visualizations or displayed inline with text.

The Sparkline - Curve feature creates a sparkline of a smoothed line chart. A sparkline is a small chart with axes and coordinates excluded. They are commonly used alongside more detailed visualizations or displayed inline with text.

Example

The examples below use data from a fast-food consumption survey. The following visualization is a sparkline of sales over time.

Burger shack sales.png

Create a Sparkline Curve in Displayr

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

Object Inspector Options

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

Inputs

OUTPUT
Chart type Curve

DATA SOURCE

All input options supported the other visualizations can be used with Sparkline. However, only the first data series in the input data will be shown (i.e. the first column in a matrix, the first variable in a variable set, or the first level in a variable used as the Group). The user can use the COLUMN MANIPULATIONS to specify a data series to show.

Chart

APPEARANCE
Fill color Color of chart
Fill opacity Opacity of the fill color is specified as a numeric between 0 (completely transparent) to 1 (opaque).
Line width Width of the line on the boundary of the area chart. Set to 0 to hide line.
Line color and opacity controls the appearance of the line in the Area chart.
Show end points Whether or not to show markers (circles) at the end points of the line in a Line, Curve or Area chart.
FONT
Global font family Default font family to be used for all labels. This can be overriden for specific text elements.
Global font color Default color for all text elements.
Font units This can be set to pt (points) or px (pixels). By default, font sizes are specified in pt, which is consistent with font sizes specified in textboxes. It is occasionally more convenient to use px because dimensions of other outputs are frequently specified in pixels.
END LABELS
Show end labels Controls whether or not labels are show at the ends of the line (None); and whether the labels are place ::Above points, Below points or Next to points
X AXIS & Y AXIS
Show X/Y axis Show the axis line and ticks.
Show X/Y axis tick labels When the axis is shown, the user can also choose whether or not include tick labels.
Tick label font Controls the font family, color and size of the tick labels.
HOVERTEXT
Number type Controls the formatting of the tick labels. Choose one of Automatic, Number and Percentage
Decimal places Controls the number of decimals shown in the tick labels.
Hover number type Control the formatting of text shown when the cursor hovers above the chart. Choose one of Automatic, Number and Percentage
Hover decimal places Controls the number of decimals shown in the hovertext.
Hover background color Controls the color of the hover text box.
BACKGROUND
Transparent background By default the background is transparent. Unselect to specify a background color and opacity.
Margins Specify space to be left around the chart in units of pixels. In most cases, margins will be automatically adjusted for the tick labels, but in certain cases, you may need to manually increase the margins to avoid tick labels being truncated.


Code

var chart_default_type = "Curve";
var default_aggregation = true;

// VERSION 2.2.1
var allow_control_groups = Q.fileFormatVersion() > 10.9;

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

var template_prompt = "Create a template to control settings for all visualizations in the document by inserting 'Visualization > Template'";
categories_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
font_list = ["Arial", "Arial Black", "Century Gothic", "Comic Sans MS",
                 "Courier New", "Georgia", "Impact", "Open Sans", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana"];
var format_alternatives = ["Automatic", "Number", "Percentage"];
var xformat_alternatives = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Custom"];
var 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"];


//// Creating the controls
var controls = [];
form.group("OUTPUT");
var qChartType = form.comboBox({name: "formChartType", label: "Chart type", alternatives: ["Area", "Box", "Column", "Curve", "Line"], default_value: chart_default_type});
var chartType = qChartType.getValue();
controls.push(qChartType);
var isLineType = ["Area", "Curve", "Line"].indexOf(chartType) != -1;
form.setHeading("Sparkline - " + chartType)

//// Data Selector.
if (allow_control_groups)
    form.group("DATA SOURCE")

// Input as linked Q or R table(s)
var outputLabel = "Output";
var outputPrompt = "Outputs are tables or other Q or R outputs";
if (displayr)
{
   outputLabel += " in 'Pages'";
   outputPrompt += " in the 'Pages' tree (top-left)";
}
var tableInput = form.dropBox({name: "formTable", label: outputLabel, types:["Table", "RItem:integer,numeric,matrix,array,data.frame,table"], required: false, multi: chartType == false, prompt: outputPrompt});

// Input as variables in the Data tab
var variables_controls = []; 
var variables_prompt = displayr ? "Variables are shown in the 'Data' tree (bottom-left)." :
       "Variables are shown in the 'Variables and Questions' tab.";

var varX = form.dropBox({label: "Variables", name: "formX", types: ['variable'], prompt: variables_prompt, multi: true, required: false});
var n_variables = 1;
if (allow_control_groups)
    n_variables = varX.getValues().length;
variables_controls.push(varX);

var n_yvariables = 0;
if (n_variables >= 1)
{
    var varY = form.dropBox({label: "Groups", name: "formY", types: ['variable'], prompt: variables_prompt, multi: false, required: false});
    if (!isEmpty(varY))
        n_yvariables = 1;
    variables_controls.push(varY);
}

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

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

// Figure out type of data input to customize data processing options
var dtype = "";
if (!allow_control_groups || !isEmpty(tableInput))
    dtype = 'table';
else if (!allow_control_groups || !isEmpty(varX) || !isEmpty(varY))
    dtype = 'variables';
else if (!isBlankSheet(pastedInput))
    dtype = 'pasted';

if (!allow_control_groups || dtype != "")
{
    if (allow_control_groups)
        form.group("DATA MANIPULATION");
    var aggregate = false;
    if (!isEmpty(varX))
    {
        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."});
        if (!allow_control_groups)
            qAggregate.lineBreakAfter = true;
        controls.push(qAggregate);
        aggregate = qAggregate.getValue();
    }
    if (true)
    {
        var qTidy = form.checkBox({label: "Automatically tidy the data", name: "formTidy", default_value: true, prompt: "Convert to numeric and simplify structure."});
        if (!allow_control_groups)
            qTidy.lineBreakAfter = true;
        tidy = qTidy.getValue();
        controls.push(qTidy);
    }
    if (aggregate && allow_control_groups && (dtype == 'table' || dtype == 'pasted'))
    {
         var qGroupLast = form.checkBox({label: "Group by last column (crosstab)", name: "formGroupByLastColumn", default_value: true, prompt: "If more than two columns are provided, automatically groups by the last column"});
         controls.push(qGroupLast);
    }
    if (aggregate && (!allow_control_groups || (dtype == 'variables' && n_variables == 2 && n_yvariables == 0)))
    { 
         var qGroupLast = form.checkBox({label: "Group by last variable (crosstab)", name: "formGroupByLastColumn", default_value: false, prompt: "If two variable are provided, automatically groups by the last variable"});
         if (!allow_control_groups)
            qGroupLast.lineBreakAfter = true;
         controls.push(qGroupLast);
    }
    if (dtype == 'pasted')
    {
        var qAutoName = form.checkBox({label: "Automatically detect row and column names", name: "formNotDataFrame", default_value: true, prompt: "Unselect to manually adjust structure of table"});
        if (!allow_control_groups)
            qAutoName.lineBreakAfter = true;
        var notDF = qAutoName.getValue();
        controls.push(qAutoName);
        if (!notDF)
        {
            var rowname_label = "First column contains row names";
            var rowname_prompt = "Values will be shown as labels in the categories axis";
            var rowname_default = false;
            if (chartType == "Scatter")
            {
                if (!scatter_mult_ys)
                {
                    rowname_label = "First column contains data labels";
                    rowname_prompt = "Values will be shown as data labels for each point (not X-axis labels)";
                }
                rowname_default = true;
            }
            var qColNames = form.checkBox({label: "First row contains column names", name: "formPastedColumnNames", default_value: true});
            var qRowNames = form.checkBox({label: rowname_label, name: "formPastedRowNames", prompt: rowname_prompt, default_value: rowname_default});
            if (!allow_control_groups)
                qColNames.lineBreakAfter = true;
            if (!allow_control_groups)
                qRowNames.lineBreakAfter = true;
            controls.push(qColNames);
            controls.push(qRowNames);
        }
    }
    qTidyLabel = form.checkBox({name: "formTidyLabels", label: "Tidy labels", default_value: true, prompt: "Extract common prefixes to simplify labels"});
    if (!allow_control_groups)
        qTidyLabel.lineBreakAfter = true;
    controls.push(qTidyLabel);
    if (chartType != 'Donut')
    {
        qTranspose = form.checkBox({name: "formTranspose", label: "Switch rows and columns", prompt: "Read each row of the input table as a data series"});
        if (!allow_control_groups)
            qTranspose.lineBreakAfter = true;
        controls.push(qTranspose);
    }

    // Creating other input controls on the Inputs page.
    var asPct = false;
    if (true)
    {
        var qAsPct = form.checkBox({name: "formAsPercentages", label: "Convert to percentages/proportions", prompt: "Compute percentages based on data in input tables (after row and column manipuations are applied)."});
        if (!allow_control_groups)
            qAsPct.lineBreakAfter = true;
        asPct = qAsPct.getValue();
        controls.push(qAsPct);
        
        // Set default formatting if 'as percentage' is checked
        if (asPct)
        {
            var formats = [values_number_formats, hover_number_formats, data_label_formats];
            for (var i = 0; i < formats.length; i++)
            {
                var format_array = formats[i];
                var pct_idx = format_array.indexOf('Percentage');
                if (pct_idx != -1) 
                {
                    format_array.splice(pct_idx, 1);
                    format_array.splice(0, 0, "Percentage");
                    var auto_idx = format_array.indexOf('Automatic');
                    if (auto_idx != -1)
                        format_array.splice(auto_idx, 1);
                }
            }
        }
        if (dtype == 'variables')
        {
            var qVarNames = form.checkBox({label: "Variable names", name:"formNames", default_value: false, prompt: "Show variable names instead of variable labels"});
            if (!allow_control_groups)
                qVarNames.lineBreakAfter;
            controls.push(qVarNames);
        }
        if (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 '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);
        }

        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."});
        if (!allow_control_groups)
            qHideEmptyRows.lineBreakAfter = true;

        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 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 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"]
            qSelectRowsOpt = form.comboBox({label: "Select rows to show by", name: "formSelectRowsOpt", alternatives: rowOpts, default_value: rowOpts[0]});
            controls.push(qSelectRowsOpt);
        }

        var qSelectRowsCtrl = form.dropBox({label: "Rows to show", name: "formSelectRowsCtrl", types: ["Control: listbox,combobox"], required: false});
            var qSelectRowsText = form.textBox({label: "Rows to show", name: "formSelectRows", type: "text", required: false, prompt: "Enter comma separated list of row indices or names"});

        if (!!qSelectRowsOpt && qSelectRowsOpt.getValue() != rowOpts[0])
            controls.push(qSelectRowsCtrl);
        else
            controls.push(qSelectRowsText);
        
        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);

        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"});
        if (!allow_control_groups)
            qRowsIgnore.lineBreakAfter = true;
        controls.push(qRowsIgnore);

        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"});
        if (!allow_control_groups)
            qHideEmptyCols.lineBreakAfter = true;
        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 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 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"]
            qSelectColsOpt = form.comboBox({label: "Select columns to show by", name: "formSelectColsOpt",
					    alternatives: colOpts, default_value: colOpts[0]});
            controls.push(qSelectColsOpt);
        }

        var qSelectColsCtrl = form.dropBox({label: "Columns to show", name: "formSelectColsCtrl",
					    types: ["Control: listbox,combobox"], required: false});
        var qSelectColsText = form.textBox({label: "Columns to show", name: "formSelectCols",
					    type: "text", required: false,
					    prompt: "Enter comma separated list of column indices or names"});
        if (!!qSelectColsOpt && qSelectColsOpt.getValue() != colOpts[0])
            controls.push(qSelectColsCtrl);
        else
            controls.push(qSelectColsText);

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

        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"});
        if (!allow_control_groups)
            qColsIgnore.lineBreakAfter = true;
        controls.push(qColsIgnore);

    }
}

if (allow_control_groups)
    form.page("Chart");
if (allow_control_groups)
	form.group("APPEARANCE");
var qTemplate = form.dropBox({name: "formTemplate", label: "Use template", types: ["RItem:AppearanceTemplate"], required: false, prompt: template_prompt});
controls.push(qTemplate);

controls.push(form.colorPicker({name: "formFillColor", label: "Fill color", default_value: "#5C9AD3"}));
if (chartType != "Box")
	controls.push(form.numericUpDown({name: "formFillOpacity", label: "Fill opacity", default_value: chartType == "Area" ? 0.4 : 1.0, minimum: 0, maximum: 1, increment: 0.1}));

if (isLineType)
{
	var qLineWidth = form.numericUpDown({name: "formLineWidth", label: "Line width", default_value: 3, minimum: 0, maximum: 20});
    controls.push(qLineWidth);
    var lineWidth = qLineWidth.getValue();
} 
if (chartType == "Area" && lineWidth > 0)
{
	controls.push(form.colorPicker({name: "formLineColor", label: "Line color", default_value: "#5C9AD3"}));
	controls.push(form.numericUpDown({name: "formLineOpacity", label: "Line opacity", default_value: 1.0, minimum: 0, maximum: 1, increment: 0.1}));
}
var labelPos = "None";
if (isLineType)
{
	var qShowEnds = form.checkBox({name: "formShowEndPts", label: "Show end points", default_value: false});
    controls.push(qShowEnds);
    var showEnds = qShowEnds.getValue();
	if (showEnds)
	{
		controls.push(form.numericUpDown({name: "formEndPtsSize", label: "End points size", default_value: 10, minimum: 1, maximum: 50}));
		controls.push(form.colorPicker({name: "formEndPtsColor", label: "End points color", default_value: "#5C9AD3"}));
	}
}

var use_default_fonts = !isEmpty(qTemplate); 
var globalFontFamily = "Arial";
var globalFontColor = "#2C2C2C";

if (allow_control_groups)
    form.group("FONT");
var qGlobalFontDefault = form.checkBox({name: "formGlobalFontDefault", label: "Use default or template font settings", default_value: use_default_fonts, prompt: template_prompt});
controls.push(qGlobalFontDefault);
use_default_fonts = qGlobalFontDefault.getValue();
if (!use_default_fonts)
{
    var qGlobalFontFamily = form.comboBox({name: "formGlobalFontFamily", label: "Global font family", alternatives: font_list, default_value: "Arial"});
    var qGlobalFontColor = form.colorPicker({name: "formGlobalFontColor", label: "Global font color", default_value: "#808080"});
    controls.push(qGlobalFontFamily);
    controls.push(qGlobalFontColor);
    var globalFontFamily = qGlobalFontFamily.getValue();
    var globalFontColor = qGlobalFontColor.getValue();
controls.push(form.comboBox({name: "formFontUnits", label: "Font units", alternatives: ["pt", "px"], default_value: "pt", prompt: "Are font sizes specified in terms of points or pixels?"}));
}

if (isLineType)
{
    if (allow_control_groups);
         form.group("End labels");
    var qLabelPos = form.comboBox({name: "formEndLabPos", label: "Show end labels", alternatives: ["None", "Above points", "Below points", "Next to points"], default_value: "None"});
    controls.push(qLabelPos);
    labelPos = qLabelPos.getValue();
    if (labelPos != "None")
    {
        var qEndLabFontDefault = form.checkBox({name: "formEndLabFontDefault", label: "Use default or template font settings", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qEndLabFontDefault);
        if (!qEndLabFontDefault.getValue())
        {
            controls.push(form.comboBox({name: "formEndLabFamily", label: "End label font family", alternatives: font_list, default_value: globalFontFamily}));
            controls.push(form.colorPicker({name: "formEndLabColor", label: "End label font color", default_value: globalFontColor}));
            controls.push(form.numericUpDown({name: "formEndLabSize", label: "End label font size", default_value: 7.5, increment: 0.5}));
        }
        controls.push(form.comboBox({name: "formEndLabNumberType", label: "Number type", alternatives: format_alternatives, default_value: format_alternatives[0], required: true}));
        controls.push(form.numericUpDown({name: "formEndLabDecimals", label: "Decimal places", default_value: 0, increment: 1, minimum: 0, maximum: 12, required: false}));
        controls.push(form.textBox({name:'formEndLabPrefix', label: 'Custom prefix', required: false, prompt: "Optional text to prepend to the end labels"}));
        controls.push(form.textBox({name:'formEndLabSuffix', label: 'Custom suffix', required: false, prompt: "Optional text to append to the end labels"}));
    }
}

if (allow_control_groups)
	form.group("X AXIS");
var showXTick = false;
var qShowX = form.checkBox({name: "formShowXAxis", label: "Show X axis", default_value: false});
controls.push(qShowX);
var showX = qShowX.getValue();
if (showX)
{
    var qShowXTick = form.checkBox({name: "formShowXTicks", label: "Show X axis tick labels", default_value: true});
    controls.push(qShowXTick);
    showXTick = qShowXTick.getValue();
    controls.push(form.colorPicker({name: "formXAxisColor", label: "X axis color", default_value: "#2C2C2C"}));
    var qXTickLen = form.numericUpDown({name: "formXTickLen", label: "X tick length", default_value: 3, minimum: 0});
    controls.push(qXTickLen);
}
if (showXTick)
{
    var qXTickFontDefault = form.checkBox({name: "formXTickFontDefault", label: "Use default or template font settings", default_value: use_default_fonts, prompt: template_prompt});
    controls.push(qXTickFontDefault);
    if (!qXTickFontDefault.getValue())
    {
        controls.push(form.comboBox({name: "formXTickFamily", label: "X tick label font family", alternatives: font_list, default_value: globalFontFamily}));
        controls.push(form.colorPicker({name: "formXTickColor", label: "X tick label font color", default_value: globalFontColor}));
        controls.push(form.numericUpDown({name: "formXTickSize", label: "X tick label font size", default_value: 7.5, increment: 0.5}));
    }
    var qXNumberType = form.comboBox({name: "formXTickNumberType", label: "Number type", alternatives: xformat_alternatives, default_value: xformat_alternatives[0], required: true});
    controls.push(qXNumberType);
    var xNumberType = qXNumberType.getValue();
    if (xNumberType == "Date/Time")
    {
        var qXDate = form.comboBox({name: "formXTickDateType", label: "Date type", alternatives: date_formats, default_value: date_formats[0], required: true});
        controls.push(qXDate);
        xNumberType = qXDate.getValue();
    }
    var qXNumberCustom = null;
    if (xNumberType == 'Custom')
    {
        qXNumberCustom = form.textBox({name: "formXTickNumberCustom", label: "Custom code", default_value: "%d %b %y", required: true, prompt: "D3 formatting code, e.g. ,.2%"});
        controls.push(qXNumberCustom);
    }
	controls.push(form.numericUpDown({name: "formXTickDecimals", label: "Decimal places", increment: 1, minimum: 0, maximum: 12, required: false}));
}

if (chartType != "Box")
{
	if (allow_control_groups)
		form.group("Y AXIS");
	var showYTick = false;
	var qShowY = form.checkBox({name: "formShowYAxis", label: "Show Y axis", default_value: false});
    controls.push(qShowY);
    showY = qShowY.getValue();
	if (showY)
	{
	    var qShowYTick = form.checkBox({name: "formShowYTicks", label: "Show Y axis tick labels", default_value: true});
            controls.push(qShowYTick);
            showYTick = qShowYTick.getValue();
	    controls.push(form.colorPicker({name: "formYAxisColor", label: "Y axis color", default_value: "#2C2C2C"}));
            var qYTickLen = form.numericUpDown({name: "formYTickLen", label: "Y tick length", default_value: 3, minimum: 0});
            controls.push(qYTickLen);
	}
	if (showYTick)
	{
        var qYTickFontDefault = form.checkBox({name: "formYTickFontDefault", label: "Use default or template font settings", default_value: use_default_fonts, prompt: template_prompt});
        controls.push(qYTickFontDefault);
        if (!qYTickFontDefault.getValue())
        {
            controls.push(form.comboBox({name: "formYTickFamily", label: "Y tick label font family", alternatives: font_list, default_value: globalFontFamily}));
            controls.push(form.colorPicker({name: "formYTickColor", label: "Y tick label font color", default_value: globalFontColor}));
            controls.push(form.numericUpDown({name: "formYTickSize", label: "Y tick label font size", default_value: 7.5, increment: 0.5}));
        }
		controls.push(form.comboBox({name: "formYTickNumberType", label: "Number type", alternatives: format_alternatives, default_value: format_alternatives[0], required: true}));
		controls.push(form.numericUpDown({name: "formYTickDecimals", label: "Decimal places", increment: 1, minimum: 0, maximum: 12, required: false}));
	}
}

if (chartType != "Box")
{
	if (allow_control_groups)
		form.group("HOVERTEXT")
	controls.push(form.comboBox({name: "formHoverNumberType", label: "Number type", alternatives: format_alternatives, default_value: format_alternatives[0], required: true}));
	controls.push(form.numericUpDown({name: "formHoverDecimals", label: "Decimal places", default_value: 2, increment: 1, minimum: 0, maximum: 12, required: false}));
	controls.push(form.colorPicker({name: "formHoverBgColor", label: "Hover background color", default_value: "#707070"}));
	controls.push(form.colorPicker({name: "formHoverFontColor", label: "Color", default_value: "#FFFFFF"}));

    var qHoverFontDefault = form.checkBox({name: "formHoverFontDefault", label: "Use default or template font settings", default_value: use_default_fonts, prompt: template_prompt});
    controls.push(qHoverFontDefault);
    if (!qHoverFontDefault.getValue())
    { 
        controls.push(form.numericUpDown({name:"formHoverFontSize", label: "Font size", minimum: 5, maximum: 100, default_value: 8.5}));
        controls.push(form.comboBox({name: "formHoverFontFamily", label: "Font family", alternatives: font_list, default_value: globalFontFamily}));
    }
}

if (allow_control_groups)
    form.group("BACKGROUND");
qBgOpt = form.checkBox({name: "formTransparent", label: "Transparent background", default_value: true});
controls.push(qBgOpt);
var bgOpt = qBgOpt.getValue();
if (!bgOpt)
{
    controls.push(form.colorPicker({name: "formBgColor", label: "Background color", default_value: "#FFFFFF"}));
	controls.push(form.numericUpDown({name: "formBgOpacity", label: "Background opacity", minimum: 0.0, maximum: 1.0, increment: 0.1, default_value: 1.0}));
}
controls.push(form.numericUpDown({name: "formMarginRight", label: "Right margin", maximum: 1000, default_value: 0}));
controls.push(form.numericUpDown({name: "formMarginLeft", label: "Left margin", maximum: 1000, default_value: 0}));
controls.push(form.numericUpDown({name: "formMarginTop", label: "Top margin", maximum: 1000, default_value: 0}));
controls.push(form.numericUpDown({name: "formMarginBottom", label: "Bottom margin", maximum: 1000, default_value: 0}));

form.setInputControls(controls);


# VERSION 2.2.0
library(flipChartBasics)
library(flipStandardCharts)
library(flipChart)

template <- get0("formTemplate")
if (is.null(template))
    template <- list(global.font = list(family = "Arial", color = "#2C2C2C", 
        size = 7.5, units = "pt"), fonts = list(`Data labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), Legend = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), Title = list(
        family = "Arial", color = "#2C2C2C", size = 12), Subtitle = list(
        family = "Arial", color = "#2C2C2C", size = 9), Footer = list(
        family = "Arial", color = "#2C2C2C", size = 6), `Categories axis title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Categories axis tick labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), `Values axis title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Values axis tick labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), `Panel title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Hover text` = list(
        family = "Arial", color = "#2C2C2C", size = 8.5)), colors = "Default colors", 
        brand.colors = NULL)
print(str(template))

# Processing all the selections from the 'Inputs' and 'Charts' tab.
select.rows <- if (exists("formSelectRowsCtrl")) formSelectRowsCtrl else get0("formSelectRows")
select.cols <- if (exists("formSelectColsCtrl")) formSelectColsCtrl else get0("formSelectCols")
pd <- PrepareData(formChartType,  QFilter, QPopulationWeight, 
    input.data.table = get0("formTable"), 
    input.data.raw = if (!exists("formX")) NULL else list(X = get0("formX"), Y = get0("formY"), Z1 = get0("formZ"), Z2 = get0("formZ2"), groups = get0("formGroups"), labels = get0("formScatterLabels")), 
    input.data.pasted = if (length(get0("formPastedData")) == 0) NULL else list(get0("formPastedData"), !get0("formNotDataFrame", ifnotfound = TRUE), get0("formPastedColumnNames"), get0("formPastedRowNames")), 
    first.aggregate = get0("formFirstAggregate", ifnotfound = FALSE), group.by.last = get0("formGroupByLastColumn", ifnotfound = FALSE), tidy = get0("formTidy", ifnotfound = FALSE), tidy.labels = get0("formTidyLabels", ifnotfound = FALSE), transpose = get0("formTranspose"), 
    row.names.to.remove = get0("formIgnoreRows"), column.names.to.remove = get0("formIgnoreCols"), hide.empty.rows = get0("formHideEmptyRows", ifnotfound = FALSE), hide.empty.columns = get0("formHideEmptyCols", ifnotfound = FALSE), select.rows = select.rows, first.k.rows = as.numeric(get0("formFirstKRows")), last.k.rows = as.numeric(get0("formLastKRows")),
 select.columns = select.cols, first.k.columns = as.numeric(get0("formFirstKCols")), last.k.columns = as.numeric(get0("formLastKCols")), 
    auto.order.rows = get0("formAutoOrderRows", ifnotfound=FALSE), sort.rows = get0("formSortRows", ifnotfound=FALSE), sort.rows.exclude = get0("formSortRowsExclude"), sort.rows.column = if (sum(nchar(get0("formSortRowsColumn"))) == 0) NULL else formSortRowsColumn, sort.rows.decreasing = get0("formSortRowsDecr"),
    auto.order.columns = get0("formAutoOrderCols", ifnotfound=FALSE), sort.columns = get0("formSortCols"), sort.columns.exclude = get0("formSortColsExclude"), sort.columns.row = if (sum(nchar(get0("formSortColsRow"))) == 0) NULL else formSortColsRow, sort.columns.decreasing = get0("formSortColsDecr"), reverse.rows = get0("formReverseRows"), reverse.columns = get0("formReverseCols"), hide.output.threshold = if (isTRUE(get0("formHideOutput"))) as.numeric(get0("formHideOutputThres")) else 0, hide.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, 
    categorical.as.binary = get0("formCategoricalAsBinary"), as.percentages = get0("formAsPercentages", ifnotfound = FALSE), show.labels = !get0("formNames", ifnotfound=TRUE), date.format = get0("formDateFormat", ifnotfound = "Automatic"))

sparkline <- Sparkline(pd$data,
	type = formChartType,
	fill.color = formFillColor,
	fill.opacity = get0("formFillOpacity"),
	line.color = get0("formLineColor"),
	line.opacity = get0("formLineOpacity"),
	line.thickness = get0("formLineWidth"),
	global.font.family = get0("formGlobalFontFamily", ifnotfound = template$global.font$family),
	global.font.color = get0("formGlobalFontColor", ifnotfound = template$global.font$color),
    font.unit = get0("formFontUnits", ifnotfound = template$global.font$units),
	end.points.show = get0("formShowEndPts"),
	end.points.symbol = "circle",
	end.points.size = get0("formEndPtsSize"),
	end.points.color = get0("formEndPtsColor"),
	end.points.opacity = 1,
	end.labels.position = get0("formEndLabPos"),
	end.labels.font.family = get0("formEndLabFamily", ifnotfound = template$fonts$`Data labels`$family),
	end.labels.font.color = get0("formEndLabColor", ifnotfound = template$fonts$`Data labels`$color),
	end.labels.font.size = get0("formEndLabSize", ifnotfound = template$fonts$`Data labels`$size),
	end.labels.prefix = get0("formEndLabPrefix"),
	end.labels.suffix = get0("formEndLabSuffix"),
	end.labels.format = ChartNumberFormat(list(get0("formEndLabNumberType"), NULL, NULL, NULL, get0("formEndLabDecimals"))),
	hover.bg.color = get0("formHoverBgColor"),
	hover.format = ChartNumberFormat(list(get0("formHoverNumberType"), NULL, NULL, NULL, get0("formHoverDecimals"))),
	hover.font.family = get0("formHoverFontFamily", ifnotfound = template$fonts$`Hover text`$family),
	hover.font.size = get0("formHoverFontSize", ifnotfound = template$fonts$`Hover text`$size),
	hover.font.color = get0("formHoverFontColor"),
	x.axis.show = get0("formShowXAxis"),
	x.axis.color = get0("formXAxisColor"),
	x.axis.width = 1,
	x.tick.show = get0("formShowXTicks", ifnotfound = FALSE),
    x.tick.length = get0("formXTickLen", ifnotfound = 0),
	y.axis.show = formShowYAxis,
    y.tick.length = get0("formYTickLen", ifnotfound = 0),
	y.axis.color = get0("formYAxisColor"),
	y.axis.width = 1,
	x.tick.font.family = get0("formXTickFamily", ifnotfound = template$fonts$`Categories axis tick labels`$family),
	x.tick.font.color = get0("formXTickColor", ifnotfound = template$fonts$`Categories axis tick labels`$color),
	x.tick.font.size = get0("formXTickSize", ifnotfound = template$fonts$`Categories axis tick labels`$size),
	x.tick.format = ChartNumberFormat(list(get0("formXTickNumberType"), get0("formXTickDateType"), get0("formXTickNumberCustom"), NULL, get0("formXTickDecimals"))),
	y.tick.font.family = get0("formYTickFamily", ifnotfound = template$fonts$`Values axis tick labels`$family),
	y.tick.font.color = get0("formYTickColor", ifnotfound = template$fonts$`Values axis tick labels`$color),
	y.tick.font.size = get0("formYTickSize", ifnotfound = template$fonts$`Values axis tick labels`$size),
	y.tick.format = ChartNumberFormat(list(get0("formYTickNumberType"), NULL, NULL, NULL, get0("formYTickDecimals"))),
	y.tick.show = get0("formShowYTicks", ifnotfound = FALSE),
    background.fill.color = get0("formBgColor", ifnotfound = "transparent"),
    background.fill.opacity = if (formTransparent) 0.0 else get0("formBgOpacity", ifnotfound = 1.0),
    margin.left = formMarginLeft,
    margin.right = formMarginRight,
    margin.top = formMarginTop,
    margin.bottom = formMarginBottom)