All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.traces.table.plot.js Maven / Gradle / Ivy

The newest version!
'use strict';

var c = require('./constants');
var d3 = require('@plotly/d3');
var Lib = require('../../lib');
var numberFormat = Lib.numberFormat;
var gup = require('../../lib/gup');
var Drawing = require('../../components/drawing');
var svgUtil = require('../../lib/svg_text_utils');
var raiseToTop = require('../../lib').raiseToTop;
var strTranslate = require('../../lib').strTranslate;
var cancelEeaseColumn = require('../../lib').cancelTransition;
var prepareData = require('./data_preparation_helper');
var splitData = require('./data_split_helpers');
var Color = require('../../components/color');

module.exports = function plot(gd, wrappedTraceHolders) {
    var dynamic = !gd._context.staticPlot;

    var table = gd._fullLayout._paper.selectAll('.' + c.cn.table)
        .data(wrappedTraceHolders.map(function(wrappedTraceHolder) {
            var traceHolder = gup.unwrap(wrappedTraceHolder);
            var trace = traceHolder.trace;
            return prepareData(gd, trace);
        }), gup.keyFun);

    table.exit().remove();

    table.enter()
        .append('g')
        .classed(c.cn.table, true)
        .attr('overflow', 'visible')
        .style('box-sizing', 'content-box')
        .style('position', 'absolute')
        .style('left', 0)
        .style('overflow', 'visible')
        .style('shape-rendering', 'crispEdges')
        .style('pointer-events', 'all');

    table
        .attr('width', function(d) {return d.width + d.size.l + d.size.r;})
        .attr('height', function(d) {return d.height + d.size.t + d.size.b;})
        .attr('transform', function(d) {
            return strTranslate(d.translateX, d.translateY);
        });

    var tableControlView = table.selectAll('.' + c.cn.tableControlView)
        .data(gup.repeat, gup.keyFun);

    var cvEnter = tableControlView.enter()
        .append('g')
        .classed(c.cn.tableControlView, true)
        .style('box-sizing', 'content-box');
    if(dynamic) {
        var wheelEvent = 'onwheel' in document ? 'wheel' : 'mousewheel';
        cvEnter
            .on('mousemove', function(d) {
                tableControlView
                    .filter(function(dd) {return d === dd;})
                    .call(renderScrollbarKit, gd);
            })
            .on(wheelEvent, function(d) {
                if(d.scrollbarState.wheeling) return;
                d.scrollbarState.wheeling = true;
                var newY = d.scrollY + d3.event.deltaY;
                var noChange = makeDragRow(gd, tableControlView, null, newY)(d);
                if(!noChange) {
                    d3.event.stopPropagation();
                    d3.event.preventDefault();
                }
                d.scrollbarState.wheeling = false;
            })
            .call(renderScrollbarKit, gd, true);
    }

    tableControlView
        .attr('transform', function(d) {return strTranslate(d.size.l, d.size.t);});

    // scrollBackground merely ensures that mouse events are captured even on crazy fast scrollwheeling
    // otherwise rendering glitches may occur
    var scrollBackground = tableControlView.selectAll('.' + c.cn.scrollBackground)
        .data(gup.repeat, gup.keyFun);

    scrollBackground.enter()
        .append('rect')
        .classed(c.cn.scrollBackground, true)
        .attr('fill', 'none');

    scrollBackground
        .attr('width', function(d) {return d.width;})
        .attr('height', function(d) {return d.height;});

    tableControlView.each(function(d) {
        Drawing.setClipUrl(d3.select(this), scrollAreaBottomClipKey(gd, d), gd);
    });

    var yColumn = tableControlView.selectAll('.' + c.cn.yColumn)
        .data(function(vm) {return vm.columns;}, gup.keyFun);

    yColumn.enter()
        .append('g')
        .classed(c.cn.yColumn, true);

    yColumn.exit().remove();

    yColumn.attr('transform', function(d) {return strTranslate(d.x, 0);});

    if(dynamic) {
        yColumn.call(d3.behavior.drag()
            .origin(function(d) {
                var movedColumn = d3.select(this);
                easeColumn(movedColumn, d, -c.uplift);
                raiseToTop(this);
                d.calcdata.columnDragInProgress = true;
                renderScrollbarKit(tableControlView.filter(function(dd) {return d.calcdata.key === dd.key;}), gd);
                return d;
            })
            .on('drag', function(d) {
                var movedColumn = d3.select(this);
                var getter = function(dd) {return (d === dd ? d3.event.x : dd.x) + dd.columnWidth / 2;};
                d.x = Math.max(-c.overdrag, Math.min(d.calcdata.width + c.overdrag - d.columnWidth, d3.event.x));

                var sortableColumns = flatData(yColumn).filter(function(dd) {return dd.calcdata.key === d.calcdata.key;});
                var newOrder = sortableColumns.sort(function(a, b) {return getter(a) - getter(b);});
                newOrder.forEach(function(dd, i) {
                    dd.xIndex = i;
                    dd.x = d === dd ? dd.x : dd.xScale(dd);
                });

                yColumn.filter(function(dd) {return d !== dd;})
                    .transition()
                    .ease(c.transitionEase)
                    .duration(c.transitionDuration)
                    .attr('transform', function(d) {return strTranslate(d.x, 0);});
                movedColumn
                    .call(cancelEeaseColumn)
                    .attr('transform', strTranslate(d.x, -c.uplift));
            })
            .on('dragend', function(d) {
                var movedColumn = d3.select(this);
                var p = d.calcdata;
                d.x = d.xScale(d);
                d.calcdata.columnDragInProgress = false;
                easeColumn(movedColumn, d, 0);
                columnMoved(gd, p, p.columns.map(function(dd) {return dd.xIndex;}));
            })
        );
    }

    yColumn.each(function(d) {
        Drawing.setClipUrl(d3.select(this), columnBoundaryClipKey(gd, d), gd);
    });

    var columnBlock = yColumn.selectAll('.' + c.cn.columnBlock)
        .data(splitData.splitToPanels, gup.keyFun);

    columnBlock.enter()
        .append('g')
        .classed(c.cn.columnBlock, true)
        .attr('id', function(d) {return d.key;});

    columnBlock
        .style('cursor', function(d) {
            return d.dragHandle ? 'ew-resize' : d.calcdata.scrollbarState.barWiggleRoom ? 'ns-resize' : 'default';
        });

    var headerColumnBlock = columnBlock.filter(headerBlock);
    var cellsColumnBlock = columnBlock.filter(cellsBlock);

    if(dynamic) {
        cellsColumnBlock.call(d3.behavior.drag()
            .origin(function(d) {
                d3.event.stopPropagation();
                return d;
            })
            .on('drag', makeDragRow(gd, tableControlView, -1))
            .on('dragend', function() {
                // fixme emit plotly notification
            })
        );
    }

    // initial rendering: header is rendered first, as it may may have async LaTeX (show header first)
    // but blocks are _entered_ the way they are due to painter's algo (header on top)
    renderColumnCellTree(gd, tableControlView, headerColumnBlock, columnBlock);
    renderColumnCellTree(gd, tableControlView, cellsColumnBlock, columnBlock);

    var scrollAreaClip = tableControlView.selectAll('.' + c.cn.scrollAreaClip)
        .data(gup.repeat, gup.keyFun);

    scrollAreaClip.enter()
        .append('clipPath')
        .classed(c.cn.scrollAreaClip, true)
        .attr('id', function(d) {return scrollAreaBottomClipKey(gd, d);});

    var scrollAreaClipRect = scrollAreaClip.selectAll('.' + c.cn.scrollAreaClipRect)
        .data(gup.repeat, gup.keyFun);

    scrollAreaClipRect.enter()
        .append('rect')
        .classed(c.cn.scrollAreaClipRect, true)
        .attr('x', -c.overdrag)
        .attr('y', -c.uplift)
        .attr('fill', 'none');

    scrollAreaClipRect
        .attr('width', function(d) {return d.width + 2 * c.overdrag;})
        .attr('height', function(d) {return d.height + c.uplift;});

    var columnBoundary = yColumn.selectAll('.' + c.cn.columnBoundary)
        .data(gup.repeat, gup.keyFun);

    columnBoundary.enter()
        .append('g')
        .classed(c.cn.columnBoundary, true);

    var columnBoundaryClippath = yColumn.selectAll('.' + c.cn.columnBoundaryClippath)
        .data(gup.repeat, gup.keyFun);

    // SVG spec doesn't mandate wrapping into a  and doesn't seem to cause a speed difference
    columnBoundaryClippath.enter()
        .append('clipPath')
        .classed(c.cn.columnBoundaryClippath, true);

    columnBoundaryClippath
        .attr('id', function(d) {return columnBoundaryClipKey(gd, d);});

    var columnBoundaryRect = columnBoundaryClippath.selectAll('.' + c.cn.columnBoundaryRect)
        .data(gup.repeat, gup.keyFun);

    columnBoundaryRect.enter()
        .append('rect')
        .classed(c.cn.columnBoundaryRect, true)
        .attr('fill', 'none');

    columnBoundaryRect
        .attr('width', function(d) { return d.columnWidth + 2 * roundHalfWidth(d); })
        .attr('height', function(d) {return d.calcdata.height + 2 * roundHalfWidth(d) + c.uplift;})
        .attr('x', function(d) { return -roundHalfWidth(d); })
        .attr('y', function(d) { return -roundHalfWidth(d); });

    updateBlockYPosition(null, cellsColumnBlock, tableControlView);
};

function roundHalfWidth(d) {
    return Math.ceil(d.calcdata.maxLineWidth / 2);
}

function scrollAreaBottomClipKey(gd, d) {
    return 'clip' + gd._fullLayout._uid + '_scrollAreaBottomClip_' + d.key;
}

function columnBoundaryClipKey(gd, d) {
    return 'clip' + gd._fullLayout._uid + '_columnBoundaryClippath_' + d.calcdata.key + '_' + d.specIndex;
}

function flatData(selection) {
    return [].concat.apply([], selection.map(function(g) {return g;}))
        .map(function(g) {return g.__data__;});
}

function renderScrollbarKit(tableControlView, gd, bypassVisibleBar) {
    function calcTotalHeight(d) {
        var blocks = d.rowBlocks;
        return firstRowAnchor(blocks, blocks.length - 1) + (blocks.length ? rowsHeight(blocks[blocks.length - 1], Infinity) : 1);
    }

    var scrollbarKit = tableControlView.selectAll('.' + c.cn.scrollbarKit)
        .data(gup.repeat, gup.keyFun);

    scrollbarKit.enter()
        .append('g')
        .classed(c.cn.scrollbarKit, true)
        .style('shape-rendering', 'geometricPrecision');

    scrollbarKit
        .each(function(d) {
            var s = d.scrollbarState;
            s.totalHeight = calcTotalHeight(d);
            s.scrollableAreaHeight = d.groupHeight - headerHeight(d);
            s.currentlyVisibleHeight = Math.min(s.totalHeight, s.scrollableAreaHeight);
            s.ratio = s.currentlyVisibleHeight / s.totalHeight;
            s.barLength = Math.max(s.ratio * s.currentlyVisibleHeight, c.goldenRatio * c.scrollbarWidth);
            s.barWiggleRoom = s.currentlyVisibleHeight - s.barLength;
            s.wiggleRoom = Math.max(0, s.totalHeight - s.scrollableAreaHeight);
            s.topY = s.barWiggleRoom === 0 ? 0 : (d.scrollY / s.wiggleRoom) * s.barWiggleRoom;
            s.bottomY = s.topY + s.barLength;
            s.dragMultiplier = s.wiggleRoom / s.barWiggleRoom;
        })
        .attr('transform', function(d) {
            var xPosition = d.width + c.scrollbarWidth / 2 + c.scrollbarOffset;
            return strTranslate(xPosition, headerHeight(d));
        });

    var scrollbar = scrollbarKit.selectAll('.' + c.cn.scrollbar)
        .data(gup.repeat, gup.keyFun);

    scrollbar.enter()
        .append('g')
        .classed(c.cn.scrollbar, true);

    var scrollbarSlider = scrollbar.selectAll('.' + c.cn.scrollbarSlider)
        .data(gup.repeat, gup.keyFun);

    scrollbarSlider.enter()
        .append('g')
        .classed(c.cn.scrollbarSlider, true);

    scrollbarSlider
        .attr('transform', function(d) {
            return strTranslate(0, d.scrollbarState.topY || 0);
        });

    var scrollbarGlyph = scrollbarSlider.selectAll('.' + c.cn.scrollbarGlyph)
        .data(gup.repeat, gup.keyFun);

    scrollbarGlyph.enter()
        .append('line')
        .classed(c.cn.scrollbarGlyph, true)
        .attr('stroke', 'black')
        .attr('stroke-width', c.scrollbarWidth)
        .attr('stroke-linecap', 'round')
        .attr('y1', c.scrollbarWidth / 2);

    scrollbarGlyph
        .attr('y2', function(d) {
            return d.scrollbarState.barLength - c.scrollbarWidth / 2;
        })
        .attr('stroke-opacity', function(d) {
            return d.columnDragInProgress || !d.scrollbarState.barWiggleRoom || bypassVisibleBar ? 0 : 0.4;
        });

    // cancel transition: possible pending (also, delayed) transition
    scrollbarGlyph
        .transition().delay(0).duration(0);

    scrollbarGlyph
        .transition().delay(c.scrollbarHideDelay).duration(c.scrollbarHideDuration)
        .attr('stroke-opacity', 0);

    var scrollbarCaptureZone = scrollbar.selectAll('.' + c.cn.scrollbarCaptureZone)
        .data(gup.repeat, gup.keyFun);

    scrollbarCaptureZone.enter()
        .append('line')
        .classed(c.cn.scrollbarCaptureZone, true)
        .attr('stroke', 'white')
        .attr('stroke-opacity', 0.01) // some browser might get rid of a 0 opacity element
        .attr('stroke-width', c.scrollbarCaptureWidth)
        .attr('stroke-linecap', 'butt')
        .attr('y1', 0)
        .on('mousedown', function(d) {
            var y = d3.event.y;
            var bbox = this.getBoundingClientRect();
            var s = d.scrollbarState;
            var pixelVal = y - bbox.top;
            var inverseScale = d3.scale.linear().domain([0, s.scrollableAreaHeight]).range([0, s.totalHeight]).clamp(true);
            if(!(s.topY <= pixelVal && pixelVal <= s.bottomY)) {
                makeDragRow(gd, tableControlView, null, inverseScale(pixelVal - s.barLength / 2))(d);
            }
        })
        .call(d3.behavior.drag()
            .origin(function(d) {
                d3.event.stopPropagation();
                d.scrollbarState.scrollbarScrollInProgress = true;
                return d;
            })
            .on('drag', makeDragRow(gd, tableControlView))
            .on('dragend', function() {
                // fixme emit Plotly event
            })
        );

    scrollbarCaptureZone
        .attr('y2', function(d) {
            return d.scrollbarState.scrollableAreaHeight;
        });

    // Remove scroll glyph and capture zone on static plots
    // as they don't render properly when converted to PDF
    // in the Chrome PDF viewer
    // https://github.com/plotly/streambed/issues/11618
    if(gd._context.staticPlot) {
        scrollbarGlyph.remove();
        scrollbarCaptureZone.remove();
    }
}

function renderColumnCellTree(gd, tableControlView, columnBlock, allColumnBlock) {
    // fixme this perf hotspot
    // this is performance critical code as scrolling calls it on every revolver switch
    // it appears sufficiently fast but there are plenty of low-hanging fruits for performance optimization

    var columnCells = renderColumnCells(columnBlock);

    var columnCell = renderColumnCell(columnCells);

    supplyStylingValues(columnCell);

    var cellRect = renderCellRect(columnCell);

    sizeAndStyleRect(cellRect);

    var cellTextHolder = renderCellTextHolder(columnCell);

    var cellText = renderCellText(cellTextHolder);

    setFont(cellText);
    populateCellText(cellText, tableControlView, allColumnBlock, gd);

    // doing this at the end when text, and text stlying are set
    setCellHeightAndPositionY(columnCell);
}

function renderColumnCells(columnBlock) {
    var columnCells = columnBlock.selectAll('.' + c.cn.columnCells)
        .data(gup.repeat, gup.keyFun);

    columnCells.enter()
        .append('g')
        .classed(c.cn.columnCells, true);

    columnCells.exit()
        .remove();

    return columnCells;
}

function renderColumnCell(columnCells) {
    var columnCell = columnCells.selectAll('.' + c.cn.columnCell)
        .data(splitData.splitToCells, function(d) {return d.keyWithinBlock;});

    columnCell.enter()
        .append('g')
        .classed(c.cn.columnCell, true);

    columnCell.exit()
        .remove();

    return columnCell;
}

function renderCellRect(columnCell) {
    var cellRect = columnCell.selectAll('.' + c.cn.cellRect)
        .data(gup.repeat, function(d) {return d.keyWithinBlock;});

    cellRect.enter()
        .append('rect')
        .classed(c.cn.cellRect, true);

    return cellRect;
}

function renderCellText(cellTextHolder) {
    var cellText = cellTextHolder.selectAll('.' + c.cn.cellText)
        .data(gup.repeat, function(d) {return d.keyWithinBlock;});

    cellText.enter()
        .append('text')
        .classed(c.cn.cellText, true)
        .style('cursor', function() {return 'auto';})
        .on('mousedown', function() {d3.event.stopPropagation();});

    return cellText;
}

function renderCellTextHolder(columnCell) {
    var cellTextHolder = columnCell.selectAll('.' + c.cn.cellTextHolder)
        .data(gup.repeat, function(d) {return d.keyWithinBlock;});

    cellTextHolder.enter()
        .append('g')
        .classed(c.cn.cellTextHolder, true)
        .style('shape-rendering', 'geometricPrecision');

    return cellTextHolder;
}

function supplyStylingValues(columnCell) {
    columnCell
        .each(function(d, i) {
            var spec = d.calcdata.cells.font;
            var col = d.column.specIndex;
            var font = {
                size: gridPick(spec.size, col, i),
                color: gridPick(spec.color, col, i),
                family: gridPick(spec.family, col, i),
                weight: gridPick(spec.weight, col, i),
                style: gridPick(spec.style, col, i),
                variant: gridPick(spec.variant, col, i),
                textcase: gridPick(spec.textcase, col, i),
                lineposition: gridPick(spec.lineposition, col, i),
                shadow: gridPick(spec.shadow, col, i),
            };
            d.rowNumber = d.key;
            d.align = gridPick(d.calcdata.cells.align, col, i);
            d.cellBorderWidth = gridPick(d.calcdata.cells.line.width, col, i);
            d.font = font;
        });
}

function setFont(cellText) {
    cellText
        .each(function(d) {
            Drawing.font(d3.select(this), d.font);
        });
}

function sizeAndStyleRect(cellRect) {
    cellRect
        .attr('width', function(d) {return d.column.columnWidth;})
        .attr('stroke-width', function(d) {return d.cellBorderWidth;})
        .each(function(d) {
            var atomicSelection = d3.select(this);
            Color.stroke(atomicSelection, gridPick(d.calcdata.cells.line.color, d.column.specIndex, d.rowNumber));
            Color.fill(atomicSelection, gridPick(d.calcdata.cells.fill.color, d.column.specIndex, d.rowNumber));
        });
}

function populateCellText(cellText, tableControlView, allColumnBlock, gd) {
    cellText
        .text(function(d) {
            var col = d.column.specIndex;
            var row = d.rowNumber;

            var userSuppliedContent = d.value;
            var stringSupplied = (typeof userSuppliedContent === 'string');
            var hasBreaks = stringSupplied && userSuppliedContent.match(/
/i); var userBrokenText = !stringSupplied || hasBreaks; d.mayHaveMarkup = stringSupplied && userSuppliedContent.match(/[<&>]/); var latex = isLatex(userSuppliedContent); d.latex = latex; var prefix = latex ? '' : gridPick(d.calcdata.cells.prefix, col, row) || ''; var suffix = latex ? '' : gridPick(d.calcdata.cells.suffix, col, row) || ''; var format = latex ? null : gridPick(d.calcdata.cells.format, col, row) || null; var prefixSuffixedText = prefix + (format ? numberFormat(format)(d.value) : d.value) + suffix; var hasWrapSplitCharacter; d.wrappingNeeded = !d.wrapped && !userBrokenText && !latex && (hasWrapSplitCharacter = hasWrapCharacter(prefixSuffixedText)); d.cellHeightMayIncrease = hasBreaks || latex || d.mayHaveMarkup || (hasWrapSplitCharacter === void(0) ? hasWrapCharacter(prefixSuffixedText) : hasWrapSplitCharacter); d.needsConvertToTspans = d.mayHaveMarkup || d.wrappingNeeded || d.latex; var textToRender; if(d.wrappingNeeded) { var hrefPreservedText = c.wrapSplitCharacter === ' ' ? prefixSuffixedText.replace(/ pTop) { pages.push(blockIndex); } pTop += rowsHeight; // consider this nice final optimization; put it in `for` condition - caveat, currently the // block.allRowsHeight relies on being invalidated, so enabling this opt may not be safe // if(pages.length > 1) break; } return pages; } function updateBlockYPosition(gd, cellsColumnBlock, tableControlView) { var d = flatData(cellsColumnBlock)[0]; if(d === undefined) return; var blocks = d.rowBlocks; var calcdata = d.calcdata; var bottom = firstRowAnchor(blocks, blocks.length); var scrollHeight = d.calcdata.groupHeight - headerHeight(d); var scrollY = calcdata.scrollY = Math.max(0, Math.min(bottom - scrollHeight, calcdata.scrollY)); var pages = findPagesAndCacheHeights(blocks, scrollY, scrollHeight); if(pages.length === 1) { if(pages[0] === blocks.length - 1) { pages.unshift(pages[0] - 1); } else { pages.push(pages[0] + 1); } } // make phased out page jump by 2 while leaving stationary page intact if(pages[0] % 2) { pages.reverse(); } cellsColumnBlock .each(function(d, i) { // these values will also be needed when a block is translated again due to growing cell height d.page = pages[i]; d.scrollY = scrollY; }); cellsColumnBlock .attr('transform', function(d) { var yTranslate = firstRowAnchor(d.rowBlocks, d.page) - d.scrollY; return strTranslate(0, yTranslate); }); // conditionally rerendering panel 0 and 1 if(gd) { conditionalPanelRerender(gd, tableControlView, cellsColumnBlock, pages, d.prevPages, d, 0); conditionalPanelRerender(gd, tableControlView, cellsColumnBlock, pages, d.prevPages, d, 1); renderScrollbarKit(tableControlView, gd); } } function makeDragRow(gd, allTableControlView, optionalMultiplier, optionalPosition) { return function dragRow(eventD) { // may come from whichever DOM event target: drag, wheel, bar... eventD corresponds to event target var d = eventD.calcdata ? eventD.calcdata : eventD; var tableControlView = allTableControlView.filter(function(dd) {return d.key === dd.key;}); var multiplier = optionalMultiplier || d.scrollbarState.dragMultiplier; var initialScrollY = d.scrollY; d.scrollY = optionalPosition === void(0) ? d.scrollY + multiplier * d3.event.dy : optionalPosition; var cellsColumnBlock = tableControlView.selectAll('.' + c.cn.yColumn).selectAll('.' + c.cn.columnBlock).filter(cellsBlock); updateBlockYPosition(gd, cellsColumnBlock, tableControlView); // return false if we've "used" the scroll, ie it did something, // so the event shouldn't bubble (if appropriate) return d.scrollY === initialScrollY; }; } function conditionalPanelRerender(gd, tableControlView, cellsColumnBlock, pages, prevPages, d, revolverIndex) { var shouldComponentUpdate = pages[revolverIndex] !== prevPages[revolverIndex]; if(shouldComponentUpdate) { clearTimeout(d.currentRepaint[revolverIndex]); d.currentRepaint[revolverIndex] = setTimeout(function() { // setTimeout might lag rendering but yields a smoother scroll, because fast scrolling makes // some repaints invisible ie. wasteful (DOM work blocks the main thread) var toRerender = cellsColumnBlock.filter(function(d, i) {return i === revolverIndex && pages[i] !== prevPages[i];}); renderColumnCellTree(gd, tableControlView, toRerender, cellsColumnBlock); prevPages[revolverIndex] = pages[revolverIndex]; }); } } function wrapTextMaker(columnBlock, element, tableControlView, gd) { return function wrapText() { var cellTextHolder = d3.select(element.parentNode); cellTextHolder .each(function(d) { var fragments = d.fragments; cellTextHolder.selectAll('tspan.line').each(function(dd, i) { fragments[i].width = this.getComputedTextLength(); }); // last element is only for measuring the separator character, so it's ignored: var separatorLength = fragments[fragments.length - 1].width; var rest = fragments.slice(0, -1); var currentRow = []; var currentAddition, currentAdditionLength; var currentRowLength = 0; var rowLengthLimit = d.column.columnWidth - 2 * c.cellPad; d.value = ''; while(rest.length) { currentAddition = rest.shift(); currentAdditionLength = currentAddition.width + separatorLength; if(currentRowLength + currentAdditionLength > rowLengthLimit) { d.value += currentRow.join(c.wrapSpacer) + c.lineBreaker; currentRow = []; currentRowLength = 0; } currentRow.push(currentAddition.text); currentRowLength += currentAdditionLength; } if(currentRowLength) { d.value += currentRow.join(c.wrapSpacer); } d.wrapped = true; }); // the pre-wrapped text was rendered only for the text measurements cellTextHolder.selectAll('tspan.line').remove(); // resupply text, now wrapped populateCellText(cellTextHolder.select('.' + c.cn.cellText), tableControlView, columnBlock, gd); d3.select(element.parentNode.parentNode).call(setCellHeightAndPositionY); }; } function updateYPositionMaker(columnBlock, element, tableControlView, gd, d) { return function updateYPosition() { if(d.settledY) return; var cellTextHolder = d3.select(element.parentNode); var l = getBlock(d); var rowIndex = d.key - l.firstRowIndex; var declaredRowHeight = l.rows[rowIndex].rowHeight; var requiredHeight = d.cellHeightMayIncrease ? element.parentNode.getBoundingClientRect().height + 2 * c.cellPad : declaredRowHeight; var finalHeight = Math.max(requiredHeight, declaredRowHeight); var increase = finalHeight - l.rows[rowIndex].rowHeight; if(increase) { // current row height increased l.rows[rowIndex].rowHeight = finalHeight; columnBlock .selectAll('.' + c.cn.columnCell) .call(setCellHeightAndPositionY); updateBlockYPosition(null, columnBlock.filter(cellsBlock), 0); // if d.column.type === 'header', then the scrollbar has to be pushed downward to the scrollable area // if d.column.type === 'cells', it can still be relevant if total scrolling content height is less than the // scrollable window, as increases to row heights may need scrollbar updates renderScrollbarKit(tableControlView, gd, true); } cellTextHolder .attr('transform', function() { // this code block is only invoked for items where d.cellHeightMayIncrease is truthy var element = this; var columnCellElement = element.parentNode; var box = columnCellElement.getBoundingClientRect(); var rectBox = d3.select(element.parentNode).select('.' + c.cn.cellRect).node().getBoundingClientRect(); var currentTransform = element.transform.baseVal.consolidate(); var yPosition = rectBox.top - box.top + (currentTransform ? currentTransform.matrix.f : c.cellPad); return strTranslate(xPosition(d, d3.select(element.parentNode).select('.' + c.cn.cellTextHolder).node().getBoundingClientRect().width), yPosition); }); d.settledY = true; }; } function xPosition(d, optionalWidth) { switch(d.align) { case 'left': return c.cellPad; case 'right': return d.column.columnWidth - (optionalWidth || 0) - c.cellPad; case 'center': return (d.column.columnWidth - (optionalWidth || 0)) / 2; default: return c.cellPad; } } function setCellHeightAndPositionY(columnCell) { columnCell .attr('transform', function(d) { var headerHeight = d.rowBlocks[0].auxiliaryBlocks.reduce(function(p, n) {return p + rowsHeight(n, Infinity);}, 0); var l = getBlock(d); var rowAnchor = rowsHeight(l, d.key); var yOffset = rowAnchor + headerHeight; return strTranslate(0, yOffset); }) .selectAll('.' + c.cn.cellRect) .attr('height', function(d) {return getRow(getBlock(d), d.key).rowHeight;}); } function firstRowAnchor(blocks, page) { var total = 0; for(var i = page - 1; i >= 0; i--) { total += allRowsHeight(blocks[i]); } return total; } function rowsHeight(rowBlock, key) { var total = 0; for(var i = 0; i < rowBlock.rows.length && rowBlock.rows[i].rowIndex < key; i++) { total += rowBlock.rows[i].rowHeight; } return total; } function allRowsHeight(rowBlock) { var cached = rowBlock.allRowsHeight; if(cached !== void(0)) { return cached; } var total = 0; for(var i = 0; i < rowBlock.rows.length; i++) { total += rowBlock.rows[i].rowHeight; } rowBlock.allRowsHeight = total; return total; } function getBlock(d) {return d.rowBlocks[d.page];} function getRow(l, i) {return l.rows[i - l.firstRowIndex];}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy