'use strict'; var c = require('./constants'); var extendFlat = require('../../lib/extend').extendFlat; var isNumeric = require('fast-isnumeric'); var isTypedArray = require('../../lib/array').isTypedArray; var isArrayOrTypedArray = require('../../lib/array').isArrayOrTypedArray; // pure functions, don't alter but passes on `gd` and parts of `trace` without deep copying module.exports = function calc(gd, trace) { var cellsValues = squareStringMatrix(trace.cells.values); var slicer = function(a) { return a.slice(trace.header.values.length, a.length); }; var headerValuesIn = squareStringMatrix(trace.header.values); if(headerValuesIn.length && !headerValuesIn[0].length) { headerValuesIn[0] = ['']; headerValuesIn = squareStringMatrix(headerValuesIn); } var headerValues = headerValuesIn .concat(slicer(cellsValues).map(function() { return emptyStrings((headerValuesIn[0] || ['']).length); })); var domain = trace.domain; var groupWidth = Math.floor(gd._fullLayout._size.w * (domain.x[1] - domain.x[0])); var groupHeight = Math.floor(gd._fullLayout._size.h * (domain.y[1] - domain.y[0])); var headerRowHeights = trace.header.values.length ? headerValues[0].map(function() { return trace.header.height; }) : [c.emptyHeaderHeight]; var rowHeights = cellsValues.length ? cellsValues[0].map(function() { return trace.cells.height; }) : []; var headerHeight = headerRowHeights.reduce(sum, 0); var scrollHeight = groupHeight - headerHeight; var minimumFillHeight = scrollHeight + c.uplift; var anchorToRowBlock = makeAnchorToRowBlock(rowHeights, minimumFillHeight); var anchorToHeaderRowBlock = makeAnchorToRowBlock(headerRowHeights, headerHeight); var headerRowBlocks = makeRowBlock(anchorToHeaderRowBlock, []); var rowBlocks = makeRowBlock(anchorToRowBlock, headerRowBlocks); var uniqueKeys = {}; var columnOrder = trace._fullInput.columnorder; if(isArrayOrTypedArray(columnOrder)) columnOrder = Array.from(columnOrder); columnOrder = columnOrder.concat(slicer(cellsValues.map(function(d, i) {return i;}))); var columnWidths = headerValues.map(function(d, i) { var value = isArrayOrTypedArray(trace.columnwidth) ? trace.columnwidth[Math.min(i, trace.columnwidth.length - 1)] : trace.columnwidth; return isNumeric(value) ? Number(value) : 1; }); var totalColumnWidths = columnWidths.reduce(sum, 0); // fit columns in the available vertical space as there's no vertical scrolling now columnWidths = columnWidths.map(function(d) { return d / totalColumnWidths * groupWidth; }); var maxLineWidth = Math.max(arrayMax(trace.header.line.width), arrayMax(trace.cells.line.width)); var calcdata = { // include staticPlot in the key so if it changes we delete and redraw key: trace.uid + gd._context.staticPlot, translateX: domain.x[0] * gd._fullLayout._size.w, translateY: gd._fullLayout._size.h * (1 - domain.y[1]), size: gd._fullLayout._size, width: groupWidth, maxLineWidth: maxLineWidth, height: groupHeight, columnOrder: columnOrder, // will be mutated on column move, todo use in callback groupHeight: groupHeight, rowBlocks: rowBlocks, headerRowBlocks: headerRowBlocks, scrollY: 0, // will be mutated on scroll cells: extendFlat({}, trace.cells, {values: cellsValues}), headerCells: extendFlat({}, trace.header, {values: headerValues}), gdColumns: headerValues.map(function(d) {return d[0];}), gdColumnsOriginalOrder: headerValues.map(function(d) {return d[0];}), prevPages: [0, 0], scrollbarState: {scrollbarScrollInProgress: false}, columns: headerValues.map(function(label, i) { var foundKey = uniqueKeys[label]; uniqueKeys[label] = (foundKey || 0) + 1; var key = label + '__' + uniqueKeys[label]; return { key: key, label: label, specIndex: i, xIndex: columnOrder[i], xScale: xScale, x: undefined, // initialized below calcdata: undefined, // initialized below columnWidth: columnWidths[i] }; }) }; calcdata.columns.forEach(function(col) { col.calcdata = calcdata; col.x = xScale(col); }); return calcdata; }; function arrayMax(maybeArray) { if(isArrayOrTypedArray(maybeArray)) { var max = 0; for(var i = 0; i < maybeArray.length; i++) { max = Math.max(max, arrayMax(maybeArray[i])); } return max; } return maybeArray; } function sum(a, b) { return a + b; } // fill matrix in place to equal lengths // and ensure it's uniformly 2D function squareStringMatrix(matrixIn) { var matrix = matrixIn.slice(); var minLen = Infinity; var maxLen = 0; var i; for(i = 0; i < matrix.length; i++) { if(isTypedArray(matrix[i])) matrix[i] = Array.from(matrix[i]); else if(!isArrayOrTypedArray(matrix[i])) matrix[i] = [matrix[i]]; minLen = Math.min(minLen, matrix[i].length); maxLen = Math.max(maxLen, matrix[i].length); } if(minLen !== maxLen) { for(i = 0; i < matrix.length; i++) { var padLen = maxLen - matrix[i].length; if(padLen) matrix[i] = matrix[i].concat(emptyStrings(padLen)); } } return matrix; } function emptyStrings(len) { var padArray = new Array(len); for(var j = 0; j < len; j++) padArray[j] = ''; return padArray; } function xScale(d) { return d.calcdata.columns.reduce(function(prev, next) { return next.xIndex < d.xIndex ? prev + next.columnWidth : prev; }, 0); } function makeRowBlock(anchorToRowBlock, auxiliary) { var blockAnchorKeys = Object.keys(anchorToRowBlock); return blockAnchorKeys.map(function(k) {return extendFlat({}, anchorToRowBlock[k], {auxiliaryBlocks: auxiliary});}); } function makeAnchorToRowBlock(rowHeights, minimumFillHeight) { var anchorToRowBlock = {}; var currentRowHeight; var currentAnchor = 0; var currentBlockHeight = 0; var currentBlock = makeIdentity(); var currentFirstRowIndex = 0; var blockCounter = 0; for(var i = 0; i < rowHeights.length; i++) { currentRowHeight = rowHeights[i]; currentBlock.rows.push({ rowIndex: i, rowHeight: currentRowHeight }); currentBlockHeight += currentRowHeight; if(currentBlockHeight >= minimumFillHeight || i === rowHeights.length - 1) { anchorToRowBlock[currentAnchor] = currentBlock; currentBlock.key = blockCounter++; currentBlock.firstRowIndex = currentFirstRowIndex; currentBlock.lastRowIndex = i; currentBlock = makeIdentity(); currentAnchor += currentBlockHeight; currentFirstRowIndex = i + 1; currentBlockHeight = 0; } } return anchorToRowBlock; } function makeIdentity() { return { firstRowIndex: null, lastRowIndex: null, rows: [] }; }