QScript Functions for Raw Data

From Q
Jump to navigation Jump to search
This page is currently under construction, or it refers to features which are under development and not yet available for use.
This page is under construction. Its contents are only visible to developers!
function variableSetRawDataTable() {
    let is_displayr = (!!Q.isOnTheWeb && Q.isOnTheWeb());
    let variable_set_label = is_displayr ? "variable set" : "question";
    
    let selected_questions = project.report.selectedQuestions();
    if (selected_questions.length == 0)
    {
        if (is_displayr)
            log("No data has been selected. Please select data in 'Data Sets' and then click this option again.");
        else
            log("No data has been selected. Please select data in the 'Variables and Questions' tab and then click this option again.");
        return;
    }
    
    let group_for_output = groupForOutput();
    let valid_questions = selected_questions.filter(hasRawData);
    let invalid_questions = selected_questions.filter(q => !hasRawData(q));
    addOutputsToDocument(valid_questions, group_for_output)
    
    if (invalid_questions.length == 1)
        log("A raw data table could not be created for the following selected " + variable_set_label + ": " +
            invalid_questions[0].name +
            " as it was either invalid, hidden or an Experiment or Ranking " + variable_set_label + ".");
    else if (invalid_questions.length > 1)
        log("Raw data tables could not be created for the following selected " + variable_set_label + "s: " +
            invalid_questions.map(x => x.name).join(", ") +
            " as they were either invalid or hidden.");
}

function groupForOutput() {
    let is_displayr = (!!Q.isOnTheWeb && Q.isOnTheWeb());
    let page = project.currentPage === undefined ? false : project.currentPage();
    if (!page) {
        if (is_displayr) {
            let page = project.report.appendPage("Blank");
            page.name = "Raw data";
        }else
            page = project.report;
    }
    return page;
}

function hasRawData(question) {
    return !question.isHidden && question.isValid &&
        !["Experiment", "Ranking"].includes(question.variableSetStructure);
}

function addRawTable(question, group, top, left, height, width) {
    let table = group.appendTable();
    table.primary = question;
    table.secondary = "RAW DATA";
    table.top = top;
    table.left = left;
    table.height = height;
    table.width = width;
    return table;
}


function addOutputsToDocument(questions, page) {
    const ROW_LABEL_COL_WIDTH = 60;
    const VARIABLE_COL_WIDTH = 85;    
    const max_variables_per_row = Math.floor((page.width - ROW_LABEL_COL_WIDTH)/VARIABLE_COL_WIDTH);
    let output_positions = calculateOutputPositions(questions, page,
						    max_variables_per_row);
    let tops, lefts, height, widths, question_order;
    if (output_positions == null) {
	// too many variable sets for one page, create another
        let new_page = page.appendGroup();
        new_page.name = "Additional Raw Data Tables";
        addOutputsToDocument(questions.slice(0,Math.floor(questions.length/2)),page);
        addOutputsToDocument(questions.slice(Math.floor(questions.length/2)),new_page);
        return;
    }else {
        tops = output_positions.top;
        lefts = output_positions.left
        height = output_positions.height;
        widths = output_positions.width;
        question_order = output_positions.question_order;
    }
    let added_outputs = question_order.map(function(question_index, index) {
        return addRawTable(questions[question_index], page, tops[index], lefts[index], height, widths[index]);
    });
    if (added_outputs.length > 0)
        project.report.setSelectedRaw([added_outputs[added_outputs.length - 1]]);
    
    return;
}

// Returns a permutation which sorts the input array into descending order
function order(arr) {
    let idxs = new Array(arr.length);
    for (let i = 0; i < arr.length; i++)
        idxs[i] = i;
    idxs.sort((a,b) => arr[b] - arr[a]);
    return idxs;
}

// Reorder an array, arr, according to the indices in the array, new_order
function reorder(arr,new_order) {
    let out = [];
    let idxs = new Array(arr.length);
    for (let i = 0; i < arr.length; i++)
        idxs[i] = i;    
    for (let i = 0; i < arr.length; i++){
        out.push(arr[idxs.indexOf(new_order[i])]);
    }
    return out;
}

function calculateOutputPositions(questions, page, max_variables_per_output) {
    const ROW_LABEL_COL_WIDTH = 60;
    const VARIABLE_COL_WIDTH = 85;
    const MARGIN_LEFT = 10;
    const MARGIN_TOP = 5;
    const MIN_OUTPUT_HEIGHT = 140;
    const page_height = page.height;
    const page_width = page.width;
    const has_title = page.subItems.length > 0 && page.subItems[0].type === "Text";
    const title_height = has_title ? page.subItems[0].height + page.subItems[0].top : 0;
    const max_rows = Math.floor((page_height - title_height)/MIN_OUTPUT_HEIGHT);
    let n_vars = questions.map(q => q.variables.length);
    let widths_needed = n_vars.map(n => ROW_LABEL_COL_WIDTH + Math.min(n, max_variables_per_output)*VARIABLE_COL_WIDTH + MARGIN_LEFT);
    let width_order = order(widths_needed);
    widths_needed = reorder(widths_needed,width_order);
    n_vars = reorder(n_vars,width_order);
    let current_row = 0;
    let lefts = [0];
    let widths = [widths_needed.shift()];
    let question_order = [width_order.shift()];
    let rows = [0];
    let current_left = widths[0];
    for (let i = 1; i < questions.length; i++) {
        next_index = widths_needed.findIndex(w => page_width - MARGIN_LEFT - current_left - w > 0);
        if (next_index > -1) {
            widths.push(widths_needed[next_index]);
            lefts.push(current_left);
            current_left += widths_needed[next_index]; 
            question_order.push(width_order[next_index]);
            widths_needed.splice(next_index,1);
            width_order.splice(next_index,1);
        }else {  // place output with most remaining variables on new row
            current_row++;
            widths.push(widths_needed.shift());
            lefts.push(0);
            current_left = widths[widths.length - 1];
            question_order.push(width_order.shift());
        }
        if (current_row >= max_rows) {
	    // reduce max allowed width of VS with many variables and try again
            if (max_variables_per_output === 2)
                return;
            else
                return(calculateOutputPositions(questions,page,max_variables_per_output-1));
        }
        rows.push(current_row);
    }
    lefts = distributeOutputsHorizontally(page,rows,lefts,widths);
    let tops = [];
    let height = (page_height - title_height - current_row*MARGIN_TOP)/(current_row+1);
    let current_top = 0;
    for (let i = 0; i < questions.length; i++) 
        tops.push(title_height + rows[i]*(height+MARGIN_TOP));
    return {top: tops, left: lefts, height: height, width: widths,question_order: question_order};
}

function distributeOutputsHorizontally(page, rows, lefts, widths) {
    const page_width = page.width;
    let new_lefts = [];
    let row_lefts, last_width_in_row, n_outputs_in_row, idx;
    idx = 0;
    for (let i = 0; i <= rows[rows.length - 1]; i++) {
        row_lefts = lefts.filter((l,idx) => rows[idx] == i);
        n_outputs_in_row = row_lefts.length
        last_width_in_row = widths[rows.lastIndexOf(i)];
        remaining_width = page_width - last_width_in_row - row_lefts[n_outputs_in_row - 1];  
        for (let j = 1; j <= row_lefts.length; j++) {
            new_lefts.push(lefts[idx]+j*remaining_width/(n_outputs_in_row+1));
            idx++;
        }         
    }
    return new_lefts;
}