Marketing - Price Sensitivity Meter

From Q
Jump to: navigation, search

Plots the Van Westendorp Price Sensitivity Meter to determine what price most people consider is reasonable.

Example

Options

Inputs

DATA SOURCE
The input data can be supplied using either Paste or type data or existing output in 'Pages'. These are expected to be tables where each row is the response to 4 columns. The columns contain the response to the question: At what price would you consider this product/brand to be 1) Too cheap, 2) Cheap, 3) Expensive, 4) Too expensive.
Alternatively, if the survey responses are present as variables in the data set, the variables can be individually selected as Price considered 'Too cheap' , Price considered 'Cheap' , Price considered 'Expensive' , Price considered 'Too expensive' . If less than 4 of these variables are provided, then only the intersections of the variables provided will be shown.

Chart

Intersection Points
Show intersection labels Label the points where the curves meet, i.e. the point of marginal cheapness, optimal price point, indifference point price and the point of marginal expensiveness.
Number of decimals Number of decimals to show in the intersection labels.
Wrap intersection labels Whether the intersection labels should be wrapped; otherwise they will be shown in a single line of text which may be quite long.
Intersection labels width (in characters) This specifies when the line breaks are introduced into the labels. Line breaks are added at the closest wordbreak before the number of characters specified.
Arrow color The color of the arrow next to the intersection labels
Arrow line width Line width of the arrow (in pixels).
Arrow length Controls the length of the arrows (and therefore how close the labels are to the intersection points).
Currency symbol The currency symbol used in the labels. These will also be used as the defaults in the CATEGORIES (X) AXIS, however, there is a separate control in that group to specify a different symbol (or no symbol).

Refer to Visualization Options for general chart formatting options.

Code

var allow_control_groups = Q.fileFormatVersion() > 10.9; // Group controls for Displayr and later versions of Q
var controls = []; // All controls displayed must be inserted here
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 <= 2) }
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]); }

// * Combo box alternatives *
var template_prompt = "Create a template to control color palettes and fonts for all visualizations in the document using " + (displayr ? "'Insert > Utilities > Visualization > Create Template'" : "'Automate > Browse Online Library > Visualization > Create Template'")
var format_prompt = "Format automatically determined based on input data. Click on up/down buttons to adjust";
categories_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
values_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
hover_number_formats = ["Automatic", "Number", "Category", "Percentage", "Metric units suffix", "Scientific", "Custom"]; // no currency since cannot add as prefix and no date because of charting requirements
data_label_formats = ["Automatic", "Number", "Percentage"];
date_formats = ["YY (Year, 2 digit)", "DD Mon YY", "DD Month YY", "DD MM YY", "YYYY (Year, 4 digit)", "DD Mon YYYY", "DD Month YYYY", "DD MM YYYY", "Mon DD YY", "Month DD YY", "MM DD YY", "Mon DD YYYY", "Month DD YYYY", "MM DD YYYY", "YY Mon DD", "YY Month DD", "YY MM DD", "YYYY Mon DD", "YYYY Month DD", "YYYY MM DD", "Custom"];
 
// * Font alternatives *
font_families = ["Andale Mono", "Arial", "Arial Black", "Century Gothic", "Comic Sans MS",
                 "Courier New", "Georgia", "Impact", "Times New Roman", "Trebuchet MS", "Verdana"]
default_colors = ["#3e7dcc", "#04b5ac", "#f5c524", "#c44e41", "#8cc0ff", "#ff905a", "#345e8c", "#04827b", "#967f47", "#96362f", "#2c4374", "#4d525a"];;
var use_default_fonts = false;
var globalFontFamily = "Arial";
var globalFontColor = "#2C2C2C";
var globalFontSize = 7.5;


/* 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";
if (displayr)
   outputLabel += " in 'Pages'";
var tableInput = form.dropBox({name: "formTable", label: outputLabel, types: ['Table', "RItem:!StandardChart!SignicanceTest:!KMeans:!phraseList:!tidyText:!wordBag"], required: false, multi:false, prompt: "Select table containing 4 columns of price data"});

// Input as variables in the Data tab
var variables_controls = []; 
var var1 = form.dropBox({label: "Price considered 'Too cheap'", name: "formV1", types: ["Variables:Numeric,Money"], prompt: "The price at which the product is so cheap you would have doubts about its quality", multi: false, required: false});
var var2 = form.dropBox({label: "Price considered 'Cheap'", name: "formV2", types: ["Variables:Numeric,Money"], prompt: "The price at which the product is inexpensive yet you have no doubts about its quality" , multi: false, required: false});
var var3 = form.dropBox({label: "Price considered 'Expensive'", name: "formV3", types: ["Variables:Numeric,Money"], prompt: "The price at which you begin to feel the product is expensive but still worth buying", multi: false, required: false});
var var4 = form.dropBox({label: "Price considered 'Too expensive'", name: "formV4", types: ["Variables:Numeric,Money"], prompt: "The price at which the product is too expensive and not worth buying", multi: false, required: false});
variables_controls.push(var1);
variables_controls.push(var2);
variables_controls.push(var3);
variables_controls.push(var4);
var anyVariables = !isEmpty(var1) || !isEmpty(var2) || !isEmpty(var3) || !isEmpty(var4)

// Pasted input data
var manual_default = [["Too cheap", "Cheap", "Expensive", "Too expensive"], ["", "", "", ""]];
var pastedInput = form.dataEntry({label: "Paste or type data", name: "formPastedData", default_value: manual_default, prompt: "Opens a spreadsheet into which you can enter data."});

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

// Creating controls on the Chart page (forced to be on Inputs tab for Q <= v5.2.1)
if (allow_control_groups)
    form.page('Chart');
if (allow_control_groups)
    form.group({label:'APPEARANCE', expanded: true});
var qTemplate = form.dropBox({name: "formTemplate", label: "Use template", types: ["RItem:AppearanceTemplate"], required: false, prompt: template_prompt});
controls.push(qTemplate);
use_default_fonts = !isEmpty(qTemplate);

form.group("Data series");
controls.push(form.colorPicker({name: "formColor1", label: "Color of Less than 'Very cheap'", default_value: "#FF0000"}));
controls.push(form.comboBox({name: "formLineType1", label: "Line type of Less than 'Very cheap'", alternatives: ["Solid", "Dot", "Dash"], default_value: "Dot"}));
controls.push(form.numericUpDown({name: "formLineWidth1", label: "Line width of Less than 'Very cheap'", default_value: 1}));

controls.push(form.colorPicker({name: "formColor2", label: "Color of Less than 'Cheap'", default_value: "#FF0000"}));
controls.push(form.comboBox({name: "formLineType2", label: "Line type of Less than 'Cheap'", alternatives: ["Solid", "Dot", "Dash"], default_value: "Solid"}));
controls.push(form.numericUpDown({name: "formLineWidth2", label: "Line width of Less than 'Cheap'", default_value: 2}));

controls.push(form.colorPicker({name: "formColor3", label: "Color of More than 'Expensive'", default_value: "#008000"}));
controls.push(form.comboBox({name: "formLineType3", label: "Line type of More than 'Expensive", alternatives: ["Solid", "Dot", "Dash"], default_value: "Solid"}));
controls.push(form.numericUpDown({name: "formLineWidth3", label: "Line width of More than 'Expensive'", default_value: 2}));

controls.push(form.colorPicker({name: "formColor4", label: "Color of More than 'Very expensive'", default_value: "#008000"}));
controls.push(form.comboBox({name: "formLineType4", label: "Line type of More than 'Very expensive", alternatives: ["Solid", "Dot", "Dash"], default_value: "Dot"}));
controls.push(form.numericUpDown({name: "formLineWidth4", label: "Line width of More than 'Very expensive'", default_value: 1}));


/* FUNCTIONS */
numberFormatControls = function(name, prefix, controls, number_formats, addPrefix = true, default_currency_symbol = "$")
{
    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
    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: default_currency_symbol, required: true}); 
    else if (yNumberType == 'Number' || yNumberType == 'Currency') 
        qNumberCustom = form.checkBox({name: "form" + name + "SeparateThousands", label: "Separate thousands by comma", default_value: true});
    if (qNumberCustom != null)
        controls.push(qNumberCustom);
    
    controls.push(form.numericUpDown({name: "form" + name + "Decimals", label: yNumberType == "Metric unit suffix" ? "Significant digits": "Decimal places", minimum: yNumberType == "Metric unit suffix" ? 1 : 0, required: false, prompt: format_prompt}));
    if (addPrefix)
    {
        controls.push(form.textBox({name: "form" + name + "Prefix", label: "Custom prefix", default_value: "", required: false, prompt: "Optional text to prepend to the labels"})); 
        controls.push(form.textBox({name: "form" + name + "Suffix", label: "Custom suffix", default_value: "", required: false, prompt: "Optional text to append to the labels"})); 
    }
    return(yNumberType);
}

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



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)
{
    var qGlobalFontFamily = form.comboBox({name: "formFont", label: "Global font family", alternatives: font_families, default_value: "Arial"});
    controls.push(qGlobalFontFamily);
    globalFontFamily = qGlobalFontFamily.getValue();
    var qGlobalFontColor = form.colorPicker({name: "formFontColor", label: !allow_control_groups ? "" : "Global font color", default_value: "#2C2C2C"});
    controls.push(qGlobalFontColor);
    globalFontColor = qGlobalFontColor.getValue();
    var 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("Intersection points");
var intersectionShow = form.checkBox({name: "formIntersectionShow", label: "Show intersection labels", prompt: "Add annotations to show points where lines intersect", default_value: true});
controls.push(intersectionShow);
if (intersectionShow.getValue())
{
    controls.push(form.numericUpDown({name: "formIntersectionLabelDecimals", label: "Number of decimals", default_value: 2}));
    formattedTextControls("IntersectionLabel", "Intersection labels", controls, 7.5, "", true, true, 21, false);
    controls.push(form.colorPicker({name: "formIntersectionArrowColor", label: "Arrow color", default_value: globalFontColor}));
    controls.push(form.numericUpDown({name: "formIntersectionArrowWidth", label: "Arrow line width", default_value: 0.7, increment: 0.1}));
    controls.push(form.numericUpDown({name: "formIntersectionArrowLength", label: "Arrow length", default_value: 10, prompt: "Increase to move labels further from intersection point"})); 
}
var currencySym = form.textBox({name: "formCurrencySymbol", label: "Currency symbol", default_value: "$", required: false});
controls.push(currencySym);


if (allow_control_groups)
    form.group("LEGEND");
var qLegShowAuto = form.comboBox({name: "formLegendShow", label: "Legend", alternatives: ["Show", "Hide"], default_value: "Show"});
controls.push(qLegShowAuto);
var hasleg = qLegShowAuto.getValue() != "Hide";

if (hasleg)
{
    fontControls("Legend", "Legend", controls)
    controls.push(form.comboBox({name: "formLegendOrientation", label: "Legend orientation", alternatives: ['Vertical', 'Horizontal'], default_value: 'Vertical'})); 
    var qLegCustom = form.checkBox({name: "formLegendCustom", label: "Customize legend position", default_value: false});
    controls.push(qLegCustom);
    var legendCustomPos = qLegCustom.getValue();

    if (legendCustomPos)
    {
        var qLegX = form.numericUpDown({name: "formLegendXPos", label: "Horizontal placement", default_value: 0.5, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (far left) to 3 (far right)"});
        controls.push(qLegX);
        var qLegY = form.numericUpDown({name: "formLegendYPos", label: "Vertical placement", default_value: -0.3, increment: 0.02, minimum: -2, maximum: 3, prompt: "Choose numeric value between -2 (below) and 3 (above)"});
        controls.push(qLegY);
    }
}

if (allow_control_groups)
    form.group('TITLE');
formattedTextControls("Title", "Title", controls, 12, "", true);
formattedTextControls("Subtitle", "Subtitle", controls, 9, "")
formattedTextControls("Footer", "Footer", controls, 6, "Optional footer. Leave blank to show default text", true, true, 100);


// X Axis
var categoriesAxisLabel = "CATEGORIES (X) AXIS";
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();
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."}));
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."}));
var qXGridWidth = form.numericUpDown({name: "formCategoriesGridWidth", label: "Grid line width", minimum: 0, default_value: 0});
controls.push(qXGridWidth);
if (qXGridWidth.getValue() > 0)
    controls.push(form.colorPicker({name: "formCategoriesGridColor", label: "Grid line color", default_value: "#E1E1E1"}));
var qXLineWidth = form.numericUpDown({name: "formCategoriesLineWidth", label: "Axis line width", minimum: 0, default_value: 0});
controls.push(qXLineWidth);
if (qXLineWidth.getValue() > 0)
    controls.push(form.colorPicker({name: "formCategoriesLineColor", label: "Axis line color", default_value: "#000000"}));
var qShowXTitle = form.checkBox({name: "formShowCategoriesTitle", label: labelShowX + " title", default_value: true});
var showCategoriesTitle = qShowXTitle.getValue();
controls.push(qShowXTitle);
if (showCategoriesTitle)
    formattedTextControls("CategoriesTitle", axisPrefix + " title", controls, 9, "", true, false, 20, true, "Price")

qShowX = form.checkBox({name: "formCategoriesAxisShow", label: labelShowX, default_value: true});
var showX = qShowX.getValue();
controls.push(qShowX);
if (showX)
{
    var categories_number_formats = ["Currency", "Number", "Category", "Percentage", "Date/Time", "Metric units suffix", "Scientific", "Custom"];
    numberFormatControls("Categories", axisPrefix, controls, categories_number_formats, true, currencySym.getValue())
    fontControls("CategoriesTick", axisPrefix + " label", controls), 
    controls.push(form.comboBox({name: "formCategoriesTickAngle", label: axisPrefix + " label orientation", alternatives: ["Automatic", "Horizontal", "Vertical", "Diagonal"], default_value: "Automatic"}));
    var qXWrap = form.checkBox({name: "formLabelWrap", label: "Wrap " + axisPrefix.toLowerCase() + " label", default_value: true});
    var xWrapOpt = qXWrap.getValue();
    controls.push(qXWrap);
    if (xWrapOpt)
        controls.push(form.numericUpDown({name: "formLabelWrapNchar", label: axisPrefix + " label width (in characters)", default_value: 21, minimum: 5, increment: 5, maximum: 1000}));
}

// Y AXIS
var valuesAxisLabel = "VALUES (Y) AXIS";
var axisPrefix = "Axis";
if (!allow_control_groups)
    axisPrefix = valuesAxisLabel.charAt(0).toUpperCase() + valuesAxisLabel.substr(1).toLowerCase();
var labelShowY = "Show " + axisPrefix.toLowerCase();

if (allow_control_groups)
    form.group(valuesAxisLabel);

controls.push(form.textBox({name: "formValuesMin", label: "Minimum value", required: false, prompt: "Leave blank to determine automatically from input data."}));
controls.push(form.textBox({name: "formValuesMax", label: "Maximum value", required: false, prompt: "Leave blank to determine automatically from input data."}));

var qYGridWidth = form.numericUpDown({name: "formValuesGridWidth", label: "Grid line width", minimum: 0, default_value: 1});
controls.push(qYGridWidth);
var showYgrid = qYGridWidth.getValue() > 0;
if (showYgrid)
    controls.push(form.colorPicker({name: "formValuesGridColor", label: "Grid line color", default_value: "#E1E1E1"}));
var qYLineWidth = form.numericUpDown({name: "formValuesLineWidth", label: "Axis line width", minimum: 0, default_value: 0});
controls.push(qYLineWidth);
if (qYLineWidth.getValue() > 0)
    controls.push(form.colorPicker({name: "formValuesLineColor", label: "Axis line color", default_value: "#000000"}));
var qShowValuesTitle = form.checkBox({name: "formShowValuesTitle", label: labelShowY + " title", default_value: true});
var showValuesTitle = qShowValuesTitle.getValue();
controls.push(qShowValuesTitle);
if (showValuesTitle)
    formattedTextControls("ValuesTitle", axisPrefix + " title", controls, 9, "", true, false, 20, true, "Proportion of respondents");
qShowY = form.checkBox({name: "formValuesAxisShow", label: labelShowY, default_value: true});
controls.push(qShowY);
var showY = qShowY.getValue();
if (showY) 
{
    var values_number_formats = ["Automatic", "Number", "Category", "Percentage", "Date/Time", "Currency", "Metric units suffix", "Scientific", "Custom"];
    var yNumberType = numberFormatControls("Values", axisPrefix, controls, values_number_formats)
    fontControls("ValuesTick", axisPrefix + " label", controls);
}

if (allow_control_groups)
    form.group("HOVER");
var hover_number_formats = ["Automatic", "Number", "Percentage"];
var hover_default_format = hover_number_formats[0];
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);
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')
    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);
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(); 
var qHoverFontFamily = form.comboBox({name: "formHoverFontFamily", label: "Hovertext font family", alternatives: font_families, default_value: globalFontFamily});
controls.push(qHoverFontFamily);
var qHoverFontSize = form.numericUpDown({name: "formHoverFontSize", label: "Hovertext font size", default_value: 8.5}); 
controls.push(qHoverFontSize);


if (allow_control_groups)
    form.group("MARGINS");
var qMarginContol = false; 
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.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 (allow_control_groups)
    form.group("BACKGROUND")
var qBg = form.checkBox({name: "formTransparent", label: "Transparent background", default_value: true}); 
controls.push(qBg);
var showBg = !qBg.getValue();
if (showBg)
    controls.push(form.colorPicker({name: "formBgColor", label: "Background color", default_value: "#FFFFFF"}));
if (showBg)
    controls.push(form.numericUpDown({name: "formBgOpacity", label: "Background opacity", minimum: 0.0, maximum: 1.0, increment: 0.1, default_value: 1.0}));
form.setInputControls(controls);


library(flipChart)
library(flipStartup)
template <- get0("formTemplate")

# Set defaults if no template
if (is.null(template))
{
    default.palette <- "Default colors"
    template <- list(colors = default.palette, global.font = list(family = "Arial", color = "#2C2C2C", 
        size = 7.5, units = "pt"), fonts = list(`Data labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), Legend = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), Title = list(
        family = "Arial", color = "#2C2C2C", size = 12), Subtitle = list(
        family = "Arial", color = "#2C2C2C", size = 9), Footer = list(
        family = "Arial", color = "#2C2C2C", size = 6), `Categories axis title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Categories axis tick labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), `Values axis title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Values axis tick labels` = list(
        family = "Arial", color = "#2C2C2C", size = 7.5), `Panel title` = list(
        family = "Arial", color = "#2C2C2C", size = 9), `Hover text` = list(
        family = "Arial", color = "#2C2C2C", size = 8.625)), colors = "Default colors")
}
  
# Processing all the selections from the 'Inputs' and 'Charts' tab.
weights <- if (length(QFilter) > 1) QPopulationWeight[QFilter] else QPopulationWeight
lenV <- max(length(get0("formV1")), length(get0("formV2")), length(get0("formV3")), length(get0("formV4")))
getVector <- function(x.name) { x <- get0(x.name); if (length(x) == 0) return(rep(NA, lenV)) else return(x)}
pd <- PrepareData("Line",  QFilter, NULL, input.data.table = get0("formTable"),
    input.data.raw = if (lenV == 0) NULL else list(A=getVector("formV1"), B=getVector("formV2"), C=getVector("formV3"), D=getVector("formV4")),
    input.data.pasted = if (!exists("formPastedData") || NROW(formPastedData) <= 2) NULL else list(get0("formPastedData")),
    hide.empty.rows.and.columns = FALSE
)

if (length(weights) > 1 && length(weights) != NROW(pd$data))
    stop("Weights should be the same length as the input data.")
attr(pd$data, "statistic") <- "%" 
pn <- PrepareNumbers(categories.format.list = list(get0("formCategoriesNumberType"), get0("formCategoriesDateType"), get0("formCategoriesNumberCustom"), get0("formCategoriesSeparateThousands"), get0("formCategoriesDecimals")), values.format.list = list(get0("formValuesNumberType"), get0("formValuesDateType"), get0("formValuesNumberCustom"), get0("formValuesSeparateThousands"), get0("formValuesDecimals")), hover.format.list = list(get0("formHoverNumberType"), get0("formHoverDateType"), get0("formHoverNumberCustom"), get0("formHoverSeparateThousands"), get0("formHoverDecimals")), data.labels.format.list = list(get0("formDataLabelNumberType"), get0("formDataLabelDateType"), get0("formDataLabelCustom"), get0("formDataLabelSeprateThousands"), get0("formDataLabelDecimals")), !is.null(attr(pd$data, "statistic")) && grepl("%", attr(pd$data, "statistic"), fixed = TRUE))

        
# Creating the chart or table
psm <- PriceSensitivityMeter(x = pd$data, weights = weights,
    # Chart: DATA SERIES
    colors = c(formColor1, formColor2, formColor3, formColor4),
    # Chart: APPEARANCE
    line.type = c(formLineType1, formLineType2, formLineType3, formLineType4),
    line.thickness = c(formLineWidth1, formLineWidth2, formLineWidth3, formLineWidth4),
    currency = formCurrencySymbol,
    # Chart: FONT
    global.font.family = get0("formFont", ifnotfound = template$global.font$family),
    global.font.color = get0("formFontColor", ifnotfound = template$global.font$color),
    # Labels for intersection points
    intersection.show = get0("formIntersectionShow", ifnotfound = TRUE),
    intersection.arrow.color = get0("formIntersectionArrowColor", ifnotfound = template$global.font$color),
    intersection.arrow.width = get0("formIntersectionArrowWidth", ifnotfound = 0.7),
    intersection.arrow.length = get0("formIntersectionArrowLength", ifnotfound = 10),
    intersection.arrow.size = 0.3,
    intersection.label.decimals = get0("formIntersectionLabelDecimals", ifnotfound = 2),
    intersection.label.font.family = get0("formIntersectionLabelFontFamily", ifnotfound = template$fonts$`Data labels`$family),
    intersection.label.font.color = get0("formIntersectionLabelFontColor", ifnotfound = template$fonts$`Data labels`$color),
    intersection.label.font.size = get0("formIntersectionLabelFontSize", ifnotfound = template$fonts$`Data labels`$size),
    intersection.label.wrap = get0("formIntersectionLabelWrap", ifnotfound=FALSE),
    intersection.label.wrap.nchar = get0("formIntersectionLabelWrapNchar"),
    #Chart: GRIDLINES
    y.line.width = get0("formValuesLineWidth", ifnotfound = 0),
    y.line.color = get0("formValuesLineColor", ifnotfound = "#000000"),
    y.grid.width = get0("formValuesGridWidth", ifnotfound = 0),
    y.grid.color = get0("formValuesGridColor", ifnotfound = "#E1E1E1"),
    x.line.width = get0("formCategoriesLineWidth", ifnotfound = 0),
    x.line.color = get0("formCategoriesLineColor", ifnotfound = "#000000"),
    x.grid.width = get0("formCategoriesGridWidth", ifnotfound = 0),
    x.grid.color = get0("formCategoriesGridColor", ifnotfound = "#E1E1E1"),
    # Chart: LEGEND
    legend.show = get0("formLegendShow", ifnotfound = TRUE),
    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.position.x = get0("formLegendXPos"),
    legend.position.y = get0("formLegendYPos"),
    # Chart: TITLE
    title = paste0("", if (sum(nchar(get0("formTitle"))) > 0) formTitle else pd$chart.title),
    title.font.family = get0("formTitleFontFamily", ifnotfound = template$fonts$`Title`$family),
    title.font.color = get0("formTitleFontColor", ifnotfound = template$fonts$`Title`$color),
    title.font.size = get0("formTitleFontSize", ifnotfound = sum(template$fonts$`Title`$size)),
    subtitle = get0("formSubtitle", ifnotfound=""),
    subtitle.font.family = get0("formSubtitleFontFamily", ifnotfound = template$fonts$Subtitle$family),
    subtitle.font.color = get0("formSubtitleFontColor", ifnotfound = template$fonts$Subtitle$color),
    subtitle.font.size = get0("formSubtitleFontSize", ifnotfound = template$fonts$Subtitle$size),
    footer = paste0("", if (sum(nchar(get0("formFooter"))) > 0) formFooter else pd$chart.footer),
    footer.font.family = get0("formFooterFontFamily", ifnotfound = template$fonts$Footer$family),
    footer.font.color = get0("formFooterFontColor", ifnotfound = template$fonts$Footer$color),
    footer.font.size = get0("formFooterFontSize", ifnotfound = template$fonts$Footer$size),
    footer.wrap = get0("formFooterWrap", ifnotfound=FALSE),
    footer.wrap.nchar = get0("formFooterWrapNchar"),
    #Chart: CATEGORIES_AXIS
    x.bounds.minimum = get0("formCategoriesMin"), 
    x.bounds.maximum = get0("formCategoriesMax"), 
    x.tick.show = get0("formCategoriesAxisShow", ifnotfound=FALSE),
    x.tick.format = pn$categories.number.format,
    x.tick.prefix = paste0("", get0("formCategoriesPrefix"), get0("formCategoriesCurrency")),    # currency is just another prefix
    x.tick.suffix = get0("formCategoriesSuffix", ifnotfound=""),
    x.title = paste0("", if (sum(nchar(get0("formCategoriesTitle", ifnotfound = " "))) > 0) get0("formCategoriesTitle", ifnotfound = " ") else pd$categories.title),
    x.title.font.family = get0("formCategoriesTitleFontFamily", ifnotfound = template$fonts$`Categories axis title`$family),
    x.title.font.color = get0("formCategoriesTitleFontColor", ifnotfound = template$fonts$`Categories axis title`$color),
    x.title.font.size = get0("formCategoriesTitleFontSize", ifnotfound = sum(template$fonts$`Categories axis title`$size)), 
    x.tick.font.family = get0("formCategoriesTickFontFamily", ifnotfound = template$fonts$`Categories axis tick labels`$family),
    x.tick.font.color = get0("formCategoriesTickFontColor", ifnotfound = template$fonts$`Categories axis tick labels`$color),
    x.tick.font.size = get0("formCategoriesTickFontSize", ifnotfound = sum(template$fonts$`Categories axis tick labels`$size)),
    x.tick.angle = if (!exists("formCategoriesTickAngle")) NULL else switch(get0("formCategoriesTickAngle"), Automatic=NULL, Horizontal=0, Vertical=90, Diagonal=45),
    x.tick.label.wrap = get0("formLabelWrap", ifnotfound=FALSE),
    x.tick.label.wrap.nchar = get0("formLabelWrapNchar", ifnotfound=100),
    #Chart: VALUES_AXIS
    y.bounds.minimum = get0("formValuesMin"), 
    y.bounds.maximum = get0("formValuesMax"), 
    y.tick.show = get0("formValuesAxisShow", ifnotfound=FALSE),
    y.tick.format = pn$values.number.format,
    y.tick.prefix = paste0("", get0("formValuesPrefix"), get0("formValuesCurrency")),    # currency is just another prefix
    y.tick.suffix = get0("formValuesSuffix", ifnotfound=""),
    y.title =  paste0("", if (sum(nchar(get0("formValuesTitle", ifnotfound=" "))) > 0) get0("formValuesTitle", ifnotfound = " ") else pd$values.title),
    y.title.font.family = get0("formValuesTitleFontFamily", ifnotfound = template$fonts$`Values axis title`$family),
    y.title.font.color = get0("formValuesTitleFontColor", ifnotfound = template$fonts$`Values axis title`$color),
    y.title.font.size = get0("formValuesTitleFontSize", ifnotfound = sum(template$fonts$`Values axis title`$size)),
    y.tick.font.size = get0("formValuesTickFontSize", ifnotfound = sum(template$fonts$`Values axis tick labels`$size)),
    y.tick.font.family = get0("formValuesTickFontFamily", ifnotfound = template$fonts$`Values axis tick labels`$family),
    y.tick.font.color = get0("formValuesTickFontColor", ifnotfound = template$fonts$`Values axis tick labels`$color),
    # Chart: HOVER
    y.hovertext.format = pn$hover.number.format,
    hovertext.font.family = get0("formHoverFontFamily", ifnotfound = template$fonts$`Hover text`$family),
    hovertext.font.size = get0("formHoverFontSize", ifnotfound = template$fonts$`Hover text`$size),
    # Chart: MARGINS
    margin.top = get0("formMarginTop"),
    margin.left = get0("formMarginLeft"),
    margin.bottom = get0("formMarginBottom"),
    margin.right = get0("formMarginRight"),
    # General arguments
    font.units = get0("formFontUnits", ifnotfound = "pt"),
    background.fill.color = get0("formBgColor", ifnotfound = "transparent"),
    background.fill.opacity = get0("formBgOpacity", ifnotfound = 1.0))