Visualization - Area - Sparkline Area

From Q
Jump to navigation Jump to search
VizIcon Area Sparkline.svg

The Sparkline - Area feature creates a sparkline of an area chart. A sparkline is a small line 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 Area Chart in Displayr

1. Go to Insert > Visualization > Sparkline > Area
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 Area

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

// VERSION 2.4.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 DEFAULT_FONT_FAMILY = "Open Sans";
var DEFAULT_FONT_COLOR = "#808080";
var DEFAULT_AXIS_COLOR = "#1C283B";

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 = !!Q.GetAvailableFontNames ? Q.GetAvailableFontNames() : ["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", visible: displayr ? false : true, alternatives: ["Area", "Box", "Column", "Curve", "Line"], default_value: "Area"});
var chartType = qChartType.getValue();
controls.push(qChartType);
var default_aggregation = chartType != "Box";
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 = "Data";
   outputPrompt = "Data is tables or other outputs in your Data Sources (bottom-left) or Report 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 found in Data Sources (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.", 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."});

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)
    {
        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}));
        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 = DEFAULT_FONT_FAMILY;
var globalFontColor = DEFAULT_FONT_COLOR;

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: DEFAULT_FONT_FAMILY, editable: true, prompt: "Select the font to use. You can also type the name of a font directly (including custom fonts)."});
    var qGlobalFontColor = form.colorPicker({name: "formGlobalFontColor", label: "Global font color", default_value: DEFAULT_FONT_COLOR});
    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, 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: "formEndLabColor", label: "End label font color", default_value: globalFontColor}));
            controls.push(form.numericUpDown({name: "formEndLabSize", label: "End label font size", default_value: 8, 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", 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();
    var qXAxisWidth = form.numericUpDown({name: "formXAxisWidth", label: "X axis line width", minimum: 0, default_value: 1});
    controls.push(qXAxisWidth);
    if (qXAxisWidth.getValue() > 0)
        controls.push(form.colorPicker({name: "formXAxisColor", label: "X axis color", default_value: DEFAULT_AXIS_COLOR}));
    var qXTickLen = form.numericUpDown({name: "formXTickLen", label: "X tick length", default_value: 3, minimum: 0});
    controls.push(qXTickLen);
    if (qXTickLen.getValue() > 0)
        controls.push(form.colorPicker({name: "formXTickMarkColor", label: "X tick color", default_value: DEFAULT_AXIS_COLOR}));
}
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, 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: "formXTickColor", label: "X tick label font color", default_value: globalFontColor}));
        controls.push(form.numericUpDown({name: "formXTickSize", label: "X tick label font size", default_value: 9, 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();
        var qYAxisWidth = form.numericUpDown({name: "formYAxisWidth", label: "Y axis line width", minimum: 0, default_value: 1});
        controls.push(qYAxisWidth);
        if (qYAxisWidth.getValue() > 0)
	        controls.push(form.colorPicker({name: "formYAxisColor", label: "Y axis color", default_value: DEFAULT_AXIS_COLOR}));
        var qYTickLen = form.numericUpDown({name: "formYTickLen", label: "Y tick length", default_value: 3, minimum: 0});
        controls.push(qYTickLen);
        if (qYTickLen.getValue() > 0)
            controls.push(form.colorPicker({name: "formYTickMarkColor", label: "Y tick color", default_value: DEFAULT_AXIS_COLOR}));
	}
	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, 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: "formYTickColor", label: "Y tick label font color", default_value: globalFontColor}));
            controls.push(form.numericUpDown({name: "formYTickSize", label: "Y tick label font size", default_value: 9, 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", 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, increment: 0.5, default_value: 9}));
        controls.push(form.comboBox({name: "formHoverFontFamily", label: "Font family", alternatives: font_list, 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 (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.4.1
library(flipChartBasics)
library(flipStandardCharts)
library(flipChart)

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

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

# 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 = 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), hide.percent.symbol = get0("formHidePercent", ifnotfound = FALSE), show.labels = !get0("formNames", ifnotfound=TRUE), date.format = get0("formDateFormat", ifnotfound = "Automatic"))

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

viz <- 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, .ifNull(get0("formEndLabDecimals"), default.digits))),
	hover.bg.color = get0("formHoverBgColor"),
	hover.format = ChartNumberFormat(list(get0("formHoverNumberType"), NULL, NULL, NULL, .ifNull(get0("formHoverDecimals"), default.digits))),
	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", ifnotfound = "transparent"),
	x.axis.width = get0("formXAxisWidth", ifnotfound = 1),
	x.tick.show = get0("formShowXTicks", ifnotfound = FALSE),
    x.tick.length = get0("formXTickLen", ifnotfound = 0),
    x.tick.color = get0("formXTickMarkColor", ifnotfound = "transparent"),
	y.axis.show = formShowYAxis,
    y.tick.length = get0("formYTickLen", ifnotfound = 0),
    y.tick.color = get0("formYTickMarkColor", ifnotfound = "transparent"),
	y.axis.color = get0("formYAxisColor", ifnotfound = "transparent"),
	y.axis.width = get0("formYAxisWidth", ifnotfound = 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)
{
    "formChartType": "Area",
    "formShowEndPts": false,
    "formEndLabPos": "None"
}