'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];}