QScript Functions for Filters
This page contains functions for creating filters.
To make these functions available when writing a QScript or Rule see JavaScript Reference.
createFiltersForAllCategories(question)
Given a question, this function creates a new filter for each category in the corresponding question.
getMergedCategoriesFromPickOne(question)
Returns an array describing which categories in the pick one (- multi) question have been merged (excluding the NET). Each entry in the array has:
- name: The name of the merged category
- values: An array of the underlying values for the categories that have been merged
getMergedCategoriesFromPickAny(question)
Returns an array describing which categories in the pick any (- multi) question have been merged (excluding the NET). Each entry in the array has:
- name: The name of the merged category
- values: An array of the underlying values for the categories that have been merged
mergePickAnyExpression(variable_names)
Generate an JavaScript expression for merged categories in a Pick Any question.
mergePickAnyCompactExpression(variable_names, codes)
Generate an JavaScript expression for merged categories in a Pick Any - Compact question.
generateUniqueControlsName(name)
Helper function for createControlsAndFilter(). Ensures that all the created controls have unique names.
recursiveGetAllControlsNamesInGroup(group_item, objects_array)
Helper function for generateUniqueControlsName(). Recursively gets all controls in the group specified by group_item, e.g. project.report, and adds them to objects_array.
determineNonNETLabels(variable)
Uses Data Reduction to determine labels to show in the controls created by createControlsAndFilter(), doesn't match up with factor levels shown to R code exactly (most noticeably, hidden codes aren't returned by this function, but are present in the R factor representation of the variable. Used by reorderHiddenCodes.
readLevelsFromRFactor(v)
Given a variable, v, this function creates a temporary R Output to read levels of the R factor representation of the variable and returns them in an array.
Helper function for readLevelsFromRFactor(). Any hidden codes in a variable will still appear in the R variable, but unfortunately the codes/levels are in the wrong order, with the hidden codes always being the last levels of the R factor. This function attempts to correct this, reordering the labels using the underlying values. levels_fromR is an array of the the levels of the R factor representation of v, a variable and levels_excluding_hidden is the result of a call to determineNonNETLabels().
getDataFileFromDependants(deps)
Determines the DataFile given the dependants of an R Output.
createControlsAndFilter(control_type)
Main function for Combo Box (Drop-Down) Filters on an Output, Text Box Filters on an Output, and List Box Filters on an Output. Prompts the user for categorical variable sets to create a filter from, adds controls to the page to modify the created filter, creates the filter as an R variable, and applies it to any Plots, Tables, or R Outputs selected on the page by the user. Valid values for control_type are "Combobox", "Textbox", or "Listbox".
Source Code
includeWeb('QScript Questionnaire Functions');
includeWeb('QScript Value Attributes Functions');
includeWeb('QScript Utility Functions');
includeWeb('QScript Selection Functions');
includeWeb('QScript Functions to Generate Outputs');
includeWeb('QScript Data Reduction Functions');
includeWeb('QScript Functions for Combining Categories');
function createFiltersForAllCategories(question) {
if (typeof(question) == "undefined" || question === null)
return null;
var q_type = question.questionType;
var new_filter_question;
var new_name = preventDuplicateQuestionName(question.dataFile, question.name + " - Filters");
var merged_categories;
// Pick Any - Grid simply gets converted to a Pick Any because there is
// no way to treat merged categories.
if (q_type == "Pick Any - Grid") {
new_filter_question = question.duplicate(new_name);
new_filter_question.questionType = "Pick Any";
} else if (q_type == "Pick One - Multi") {
// Flatten and merge using existing methods
var merged_categories = getMergedCategoriesFromPickOne(question);
if (merged_categories == null || merged_categories.length == 0)
return null;
new_filter_question = pickOneMultiToPickAnyFlattenAndMergeByRows(question, merged_categories, true, false);
new_filter_question.name = preventDuplicateQuestionName(question.dataFile, new_filter_question.name.replace("(flattened)", "- Filters"));
new_filter_question.questionType = "Pick Any - Grid";
if (!new_filter_question.isValid)
{
new_filter_question.questionType = "Pick Any";
}
} else {
// For other question types we work out which categories are
// in the data reduction (excluding the main NET).
if (["Pick One", "Pick Any - Compact"].indexOf(q_type) > -1)
merged_categories = getMergedCategoriesFromPickOne(question);
else if (q_type == "Pick Any")
merged_categories = getMergedCategoriesFromPickAny(question);
else
throw "Not applicable for " + q_type + "questions.";
if (merged_categories == null)
return null;
// Create a new variable for each category in the data reduction
var variables = question.variables;
var data_file = question.dataFile;
var new_vars = [];
var num_vars = variables.length;
var last_var = variables[num_vars-1];
var new_name_prefix = "FILTER" + makeid() +"_";
merged_categories.forEach(function (obj, ind) {
var expression;
if(q_type == "Pick Any")
expression = mergePickAnyExpression(obj.variables);
else if (q_type == "Pick Any - Compact")
expression = mergePickAnyCompactExpression(variables.map(function (v) { return v.name; }), obj.values);
else if (q_type == "Pick One")
expression = mergePickOneExpression(variables[0].name, obj.values, true);
try {
var new_var = question.dataFile.newJavaScriptVariable(expression,
false,
new_name_prefix + ind + 1,
obj.name,
last_var,
{skipValidation: true, accelerate: true});
} catch (e) {
log("Could not create filter: " + e);
return false;
}
new_var.variableType = "Categorical";
last_var = new_var;
new_vars.push(new_var);
});
// Form the question
if (new_vars.length > 0) {
var new_q_name = preventDuplicateQuestionName(data_file, question.name + " - Filters");
var new_filter_question = data_file.setQuestion(new_q_name, "Pick Any", new_vars);
setCountThisValueForVariablesInQuestion(new_filter_question, 1, true);
new_filter_question.needsCheckValuesToCount = false;
} else
return null;
}
// Set the properties of the new question
new_filter_question.isFilter = true;
setLabelForVariablesInQuestion(new_filter_question, 0, "Not Selected");
setLabelForVariablesInQuestion(new_filter_question, 1, "Selected");
return new_filter_question;
}
// Returns an array describing which categories in the pick one (- multi)
// question have been merged (excluding the NET). Each entry in the array has:
// - name: The name of the merged category
// - values: An array of the underlying values for the categories that have
// been merged
function getMergedCategoriesFromPickOne(q) {
var value_attributes = q.valueAttributes;
var non_missing_values = q.uniqueValues.filter(function (x) {
return !value_attributes.getIsMissingData(x);
}).sort();
// Get the set of values for each code in the data reduction
var merging_objects = getAllUnderlyingValues(q);
// Filter out the set of values corresoponding to the NET as
// we don't want to keep them.
merging_objects = merging_objects.filter(function (obj) {
return obj.array.sort().toString() != non_missing_values.toString();
});
merging_objects = merging_objects.map(function (obj) {
return { name: obj.label, values: obj.array };
});
return merging_objects;
}
// Returns an array describing the data reduction of a pick any question
// excluding the NET. Each entry in the array corresponds to a code from
// the data reduction, and has:
// - name: The name of the category.
// - variables: The names of the variables in the category.
function getMergedCategoriesFromPickAny(q) {
var data_reduction = q.dataReduction;
var net_labels = ["NET"];
if (fileFormatVersion() > 8.41)
net_labels = data_reduction.netRows.map(function (x) { return data_reduction.rowLabels[x]; });
var merging_objects = [];
data_reduction.rowLabels.forEach(function (label) {
if (net_labels.indexOf(label) == -1)
merging_objects.push({ name: label, variables: data_reduction.getUnderlyingVariables(label).map(function (v) { return v.name; }) });
});
return merging_objects;
}
// Generate an JavaScript expression for merged categories in a Pick Any question.
function mergePickAnyExpression(variable_names) {
var nan_bit = "isNaN(" + variable_names[0] + ")";
var main_bit = "(" + variable_names[0];
if (variable_names.length > 1) {
for (var j = 1; j < variable_names.length; j++) {
nan_bit += " || isNaN(" + variable_names[j] + ")";
main_bit += " || " + variable_names[j];
}
}
return nan_bit + " ? NaN : " + main_bit + ") ? 1 : 0;";
}
// Generate an JavaScript expression for merged categories in a Pick Any - Compact question.
function mergePickAnyCompactExpression(variable_names, codes) {
var missing_check = "(" + variable_names.map(function (name) { return "Q.IsMissing(" + name + ")"; } ).join(" && ") + ") ? NaN : (";
var term_array = [];
variable_names.forEach(function (name) {
codes.forEach(function (code) {
term_array.push("Q.Source(" + name + ") == " + code);
});
});
return missing_check + term_array.join(" || ") + ") ? 1 : 0;";
}
function generateUniqueControlsName(name) {
var controls = [];
recursiveGetAllControlsNamesInGroup(project.report, controls);
if (controls.indexOf(name) == -1)
return name;
var nonce = 1;
while (controls.indexOf(name + "." + nonce.toString()) != -1)
++nonce;
return name + "." + nonce.toString();
}
function recursiveGetAllControlsNamesInGroup(group_item, objects_array) {
var cur_sub_items = group_item.subItems;
for (var j = 0; j < cur_sub_items.length; j++) {
if (cur_sub_items[j].type == 'ReportGroup') {
recursiveGetAllControlsNamesInGroup(cur_sub_items[j], objects_array);
}
else if (cur_sub_items[j].type == 'Control') {
objects_array.push(cur_sub_items[j].referenceName);
objects_array.push(cur_sub_items[j].name);
}
}
}
// Uses QScript Data Reduction to determine labels
// to show in the List Boxes, doesn't match up with factor levels
// shown to R code exactly (most noticeably, hidden codes aren't
// returned by this function, but are present in the R factor
// Hence, this is only used as a fallback if simply reading
// the levels from the factor fails (it shouldn't)
function determineNonNETLabels(variable) {
var vals = getAllUnderlyingValues(variable.question);
if (vals === null)
return null;
var labels = [];
for (var i = 0; i < vals.length; i++) {
var val_arr = vals[i].array;
var isNET = false;
if (val_arr.length > 1) {
for (var j = 0; j < vals.length; j++) {
if (i != j && vals[j].label !== "NET")
isNET = val_arr.some(v => vals[j].array.indexOf(v) >= 0);
if (isNET)
break;
}
}
if (!isNET)
labels.push(vals[i].label);
}
return labels;
}
function readLevelsFromRFactor(v) {
const new_qname = "questionadffewe245";
const new_vname = "variableREWRRE12323112";
var data_file = v.question.dataFile;
// var expression = 'xxxxx122345xxx <- ' + stringToRName(question.name);
var expression = 'xxxxx122345foo <- ' + generateDisambiguatedVariableName(v);
try {
var new_r_question = data_file.newRQuestion(expression, new_qname, new_vname,
data_file.variables[data_file.variables.length - 1]);
// var r_output = group.appendR(expression);
//var levels = r_output.data.getAttribute([], "levels");
var value_attributes = new_r_question.valueAttributes;
var unique_values = new_r_question.uniqueValues;
var labels = unique_values.filter(u => !value_attributes.getIsMissingData(u)).map(u => value_attributes.getLabel(u));
var levels_excluding_hidden = determineNonNETLabels(v);
if (levels_excluding_hidden.length < labels.length)
labels = reorderHiddenCodes(labels, levels_excluding_hidden, v);
new_r_question.deleteQuestion();
return labels;
}catch(e){
log(e.message);
return determineNonNETLabels(v);
}
}
// Hidden codes are included in the levels of the R factor representation
// of a categorical variable, however they are positioned as the last
// level(s) of the factor. This function reorders the R factor levels according
// to the source values in the value attributes for the variable
function reorderHiddenCodes(levels_fromR, levels_excluding_hidden, v) {
var value_attributes = v.question.valueAttributes;
// A NET label is only included in the R factor if all its components are hidden,
// in this case the hidden components are not included, so levels_fromR
// may contain NETs which are then included in possibly_hidden_labels below
// getSourcecValueByLabel throws an error when its argument is a NET label
var possibly_hidden_labels = levels_fromR.slice(levels_excluding_hidden.length, levels_fromR.length);
var hidden_vals = possibly_hidden_labels.map(function(l){ try{
return value_attributes.getValue(value_attributes.getSourceValueByLabel(l))
}catch(e){
return "NET";
}});
let hidden_labels = possibly_hidden_labels.filter((v,i) => hidden_vals[i] !== "NET");
hidden_vals = hidden_vals.filter(v => v !== "NET");
if (hidden_vals.length === 0)
return levels_fromR;
var dr = v.question.dataReduction;
var lvl_sourcevals = levels_fromR.map(function(l){ try {
var sv = [value_attributes.getSourceValueByLabel(l)];
}catch(e){ // merge
var sv = dr.getUnderlyingValues(l);
}
return sv;})
// Use mean of component values for merged categories
var lvl_vals = lvl_sourcevals.map(function(sv){
return sv.reduce((v1,v2) => value_attributes.getValue(v1)+value_attributes.getValue(v2),0)/sv.length;
});
var unhidden_lvl_vals = lvl_vals.slice(0, lvl_vals.length - hidden_vals.length);
// for each hidden val, determine where to splice into label array
var pos = 0;
var hv;
for (var i = 0; i < hidden_vals.length; i++) {
hv = hidden_vals[i];
for (var j = 0; j < unhidden_lvl_vals.length; j++) {
if (hv < unhidden_lvl_vals[j]) {
break;
}else
pos += 1;
}
unhidden_lvl_vals.splice(pos, 0, hv);
levels_excluding_hidden.splice(pos, 0, hidden_labels[i]);
pos = 0;
}
return levels_excluding_hidden;
}
function createControlsAndFilter(control_type) {
includeWeb("QScript Selection Functions");
includeWeb("QScript Utility Functions");
includeWeb("QScript Functions to Generate Outputs");
includeWeb("QScript R Output Functions");
includeWeb("QScript Value Attributes Functions");
includeWeb("QScript Data Reduction Functions");
// On the web just take from what is selected.
const is_displayr = inDisplayr();
if (!is_displayr)
{
log("Sorry, this feature is only available in Displayr.");
return false;
}
let is_cb = control_type === "Combobox";
let is_tb = control_type === "Textbox";
let is_date = control_type === "Date";
let group;
let data_file;
if (project.report.selectedRaw().length === 0) {
log("Please select the Table(s), Plot(s), and/or R Output(s) to apply the filter to.");
return false;
} else
group = project.currentPage();
let questions_in_tables = getQuestionsSelectedInTables();
// Remove empty tables
questions_in_tables = questions_in_tables.filter(function (q) { return q.question != null; });
// Need extra work to determine the data file if the user has no Q Plots or Tables
// but only R Outputs selected
let sub_items = project.report.selectedRaw(); // group.subItems;
let types = sub_items.map(i => i.type);
let routput_selected = types.indexOf("R Output") > -1;
if (questions_in_tables.length === 0) {
if (project.dataFiles.length === 0) {
log("You must add a Data Set to use this feature.");
return false;
} else if (project.dataFiles.length === 1)
data_file = project.dataFiles[0];
else {
// Try to get dataFile from dependents in the r outputs; otherwise, prompt user
data_file;
if (routput_selected) {
let r_outputs = sub_items.filter(i => i.type === "R Output");
for (let i = 0; i < r_outputs.length; i++) {
data_file = getDataFileFromDependants(r_outputs[i].dependants());
if (data_file !== null)
break;
}
}
if (!data_file)
data_file = selectOneDataFile('Select the data file of the variable sets you wish to use for the filter.', project.dataFiles);
}
} else {
data_file = questions_in_tables[0].question.dataFile;
// Make sure all questions are from the same data set
if (!questions_in_tables.map(function (q) { return q.question.dataFile.name; }).every(function (type) { return type == data_file.name; })) {
log("All of the selected outputs must be from the same data file.");
return false;
}
}
let allowed_types = is_tb ? ["Text", "Text - Multi"] : (is_date ? ["Date"] :
["Pick One", "Pick Any", "Pick One - Multi"]);
let candidate_questions = getAllQuestionsByTypes([data_file], allowed_types);
if (candidate_questions.length == 0) {
log("No appropriate variable sets found in the data file for the selected output.");
return false;
}
let selected_questions = [];
let prompt = "Select " + (is_date ? "Date variable" : "variable sets") + " to use for the filter:";
while (selected_questions.length === 0) {
selected_questions = selectManyQuestions(prompt, candidate_questions, true).questions;
if (selected_questions.length === 0)
alert("Please select at least one Variable Set from the list to continue.");
}
let multiple_selection = (is_cb ? askYesNo("Should the user be allowed to select more than one category for each variable set?") : true);
let ignore_case = (is_tb ? askYesNo("Ignore case (e.g., match \"dog\" with \"Dog\")?") : true);
/////////////////////////////////////////////////////////////////////////////////////
// Add Controls to page
let filter_objects = [];
selected_questions.forEach(function (q) {
if (["Pick Any", "Date"].includes(q.questionType)) {
filter_objects.push( {data: q, type: "multi", question: q});
} else {
let q_vars = q.variables;
q_vars.forEach(function (v) {
filter_objects.push({data: v, type: "single", question: q});
})
}
})
let n_boxes = filter_objects.length;
let lbs = [];
let above;
const rowPad = 10;
const height = is_cb || is_tb || is_date ? 25 : 100;
let lb_names = [];
let end_date_names = [];
let control_lab, control_name;
if (!is_date) {
control_lab = control_type.substring(0, control_type.length - 3) + " Box";
control_name = control_type.substring(0, control_type.length - 3) + "Box";
}else {
control_lab = "Date Control";
control_name = "DateControl";
}
// DS-3217: Control names must be unique, but some users move controls
// to a page master slide. These cannot be found via QScript/recursively
// searching project.report). Hence, resort to try-catch/while loop until
// a unique name is generated
function _setUniqueControlName(ctrl, ref_name) {
let unique_name_found = false;
let duplicate_count = 0;
while (!unique_name_found) {
let ref_name_unique = ref_name + (duplicate_count > 0 ? ("." + duplicate_count.toString()) : "");
try {
ctrl.referenceName = ref_name_unique;
unique_name_found = true;
}catch(e){
duplicate_count++;
}
}
ctrl.name = ctrl.referenceName;
return;
}
filter_objects.forEach(function (obj, ind) {
let v = obj.data;
let dates;
let ctrl = group.appendControl(control_type);
if (is_tb) {
ctrl.text = "";
} else if (is_date) {
dates = v.uniqueValues;
dates = dates.filter(x => !isNaN(x));
let min_date = arrayMin(dates);
ctrl.date = min_date;
} else {
if (obj.type == "multi") {
let data_reduction = v.dataReduction;
let row_labels = data_reduction.rowLabels;
let net_rows = data_reduction.netRows;
row_labels = row_labels.filter(function (label, index) {
return net_rows.indexOf(index) == -1;
});
requireControlLabelsAreUnique(row_labels, obj.question, ctrl);
ctrl.itemList = row_labels;
ctrl.selectedItems = row_labels;
} else {
let value_attributes = v.valueAttributes;
let has_missing_values = v.uniqueValues.some(x =>
value_attributes.getIsMissingData(x));
var labels = readLevelsFromRFactor(v);
labels = labels.map(lbl => lbl.trim());
if (labels === null)
{
log("Variable " + v.name + " contains no categories");
return false;
}
if (has_missing_values)
labels.push("Missing data");
requireControlLabelsAreUnique(labels, obj.question, ctrl);
ctrl.itemList = labels;
ctrl.selectedItems = labels;
}
ctrl.selectionMode = multiple_selection ? "MultipleSelection" : "SingleSelection";
}
let ref_name;
if (is_date)
ref_name = generateUniqueControlsName(cleanVariableName("Start" +
control_name + v.variables[0].label));
else
ref_name = generateUniqueControlsName(cleanVariableName(control_name +
(obj.type == "multi" ? v.name : v.label) ));
_setUniqueControlName(ctrl, ref_name);
lb_names.push(ctrl.name);
lbs.push(ctrl);
ctrl.top = ind == 0 ? 0 : above.top + height + rowPad;
ctrl.height = height;
ctrl.left = 0;
above = ctrl;
if (is_date) { // Add end date control and control titles for date filters
let ctrl_title = group.appendText();
let html = Q.htmlBuilder();
html.appendParagraph("From");
ctrl_title.content = html;
ctrl_title.height = height;
ctrl_title.left = 0;
ctrl_title.top = ctrl.top;
ctrl_title.width = ctrl.width;
ctrl.top = ctrl.top + height;
var ctrl2 = group.appendControl("Date");
let max_date = arrayMax(dates);
ctrl2.date = max_date;
ref_name = generateUniqueControlsName(cleanVariableName("EndDateControl" +
v.variables[0].label));
_setUniqueControlName(ctrl2,ref_name);
end_date_names.push(ctrl2.name);
ctrl_title = group.appendText();
html = Q.htmlBuilder();
html.appendParagraph("To");
ctrl_title.content = html;
ctrl_title.height = height;
ctrl_title.left = 0;
ctrl_title.top = above.top + height + rowPad;
ctrl_title.width = ctrl.width;
ctrl2.top = ctrl_title.top + height;
ctrl2.height = height;
ctrl2.left = 0;
above = ctrl2;
}
});
//////////////////////////////////////////////////////////////////////////////
// Create filter as a new R Question
let lab = control_lab + " Filter " + selected_questions.map(function(q) {
return q.questionType == "Pick Any" ? q.name : q.variables.map(function(v) { return v.label;}).join(" + "); }).join(" + ");
let new_question_name = preventDuplicateQuestionName(data_file, lab);
let vname = control_name.toLowerCase() + "_filter_" +
selected_questions.map(function(q) {
return q.name.replace(/[^a-zA-Z0-9_@\#\$\\]/g, '_').toLowerCase();
}).join("_");
let new_var_name = preventDuplicateVariableName(data_file, vname);
let r_expr = "";
let new_r_question;
let multiple_boxes = n_boxes > 1;
if (multiple_boxes)
r_expr += "(";
if (is_tb) {
r_expr = ("IGNORE_CASE = " + (ignore_case ? "TRUE\n" : "FALSE\n"));
r_expr += "caseFun <- if (IGNORE_CASE) tolower else I\n";
}
for (let i = 0; i < n_boxes; i++) {
let is_multi = filter_objects[i].type == "multi";
let data = filter_objects[i].data;
let name_in_code = is_multi ? generateDisambiguatedQuestionName(data) : generateDisambiguatedVariableName(data);
if (is_tb)
r_expr += `grepl(caseFun(\`${lb_names[i]}\`), caseFun(${name_in_code}), fixed = TRUE)`;
else if (is_date)
r_expr += `${name_in_code} >= \`${stringToRName(lb_names[i])}\` & `+
`${name_in_code} <= \`${stringToRName(end_date_names[i])}\``;
else if (is_multi)
r_expr += `rowSums(${name_in_code}[ , trimws(${stringToRName(lb_names[i])},
whitespace = "[\\\\h\\\\v]"), drop = FALSE]) > 0`;
else
r_expr += RCodeForNominal(lb_names[i],name_in_code);
if (i < n_boxes - 1)
r_expr += multiple_boxes ? ") & \n (" : " & ";
}
if (multiple_boxes)
r_expr += ")";
try {
new_r_question = data_file.newRQuestion(r_expr, new_question_name, new_var_name, null);
} catch (e) {
function errorFun(e){
log("The filter could not be created for the selected variable sets: " + e.message);
return false;
}
if (/(R code is ambiguous)|(There are [0-9]* cases in this result)/.test(e.message)){
r_expr = "";
if (is_tb) {
r_expr = ("IGNORE_CASE = " + (ignore_case ? "TRUE\n" : "FALSE\n"));
r_expr += "caseFun <- if (IGNORE_CASE) tolower else I\n";
}
if (multiple_boxes)
r_expr += "(";
for (var i = 0; i < n_boxes; i++) {
let data = filter_objects[i].data;
(r_expr += is_tb ?
("grepl(caseFun(`" + lb_names[i] + "`), caseFun(" + generateDisambiguatedVariableName(data) + "), fixed = TRUE)") :
(RCodeForNominal("`"+lb_names[i]+"`",generateDisambiguatedVariableName(data))));
if (i < n_boxes - 1)
r_expr += multiple_boxes ? ") & \n (" : " & ";
}
if (multiple_boxes)
r_expr += ")";
try {
new_r_question = data_file.newRQuestion(r_expr, new_question_name, new_var_name, null);
} catch(e){
return errorFun(e);
}
}else{
return errorFun(e);
}
}
// New properties added for https://numbers.atlassian.net/browse/FS1-741 are not available in versions older than 25.24
if (fileFormatVersion() < 25.24) {
new_r_question.isHidden = true;
} else {
new_r_question.isHiddenInView = true
new_r_question.isHiddenInExplore = true
}
new_r_question.isFilter = true;
new_r_question.questionType = "Pick One";
insertAtHoverButtonIfShown(new_r_question);
///////////////////////////////////////////////////////////////////////////////////
// Apply filter to the selected outputs
const allowed_output_types = ["Plot", "R Output", "Table"];
if (types.filter(function(t){return allowed_output_types.indexOf(t) === -1;}).length > 0)
log("Some selections were not a Chart, Table or R Output and the created filter has not been applied to them.");
for (let i = 0; i < sub_items.length; i++)
if (allowed_output_types.indexOf(types[i]) > -1) {
let has_nested_table = sub_items[i].type == "R Output" && sub_items[i].subItems.length > 0 && sub_items[i].subItems[0].type == "Table";
let filter_item = has_nested_table ? sub_items[i].subItems[0] : sub_items[i];
if (filter_item.filters !== null)
filter_item.filters = filter_item.filters.concat(new_r_question.variables);
}
return true;
}
function requireControlLabelsAreUnique(labels, question, control) {
let duplicates = findDuplicateLabels(labels);
if (duplicates.length == 0)
return true;
control.deleteItem();
let unique_duplicates = uniqueElementsInArray(duplicates).join(", ");
let msg = `The selected data, ${question.name}, cannot be used to create a filter as it contains multiple categories that have the same label but different data: ${unique_duplicates}`;
throw new UserError(msg);
}
function findDuplicateLabels(labels) {
return labels.filter((item, index) => labels.indexOf(item) !== index);
}
function RCodeForNominal(control_name,name_in_code) {
return `("Missing data" %in% ${stringToRName(control_name)})*is.na(${name_in_code}) |
trimws(${name_in_code}, whitespace = "[\\\\h\\\\v]") %in% ${stringToRName(control_name)}`;
}