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!
includeWeb('QScript Utility Functions');
includeWeb('QScript Selection Functions');
function variableSetRawDataTable() {
    let is_displayr = inDisplayr();

    let selected_questions = getAllUserSelections().selected_questions;
    let variable_set_label = is_displayr ? "variable set" : "question";
    if (selected_questions.length === 0)
    {
        let msg = 'No data has been selected. Please select data in the ';
        msg += is_displayr ? '"Data Sets"' : '"Variables and Questions" tab';
        msg += 'and then click this option again.';
        log(msg)
        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 = inDisplayr();
    let page = project.currentPage === undefined ? false : project.currentPage();
    if (!page) {
        if (is_displayr) {
            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);
    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;
    }
    let tops = output_positions.top;
    let lefts = output_positions.left
    let height = output_positions.height;
    let widths = output_positions.width;
    let 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;
            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);
    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;
}