Skip to content

Commit da37b4f

Browse files
authored
Merge pull request #2511 from plotly/ragged-table
Support ragged tables and show full table borders
2 parents d3ff96b + 4936e20 commit da37b4f

14 files changed

+92
-163
lines changed

src/traces/table/data_preparation_helper.js

+63-10
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,28 @@ var isNumeric = require('fast-isnumeric');
1414

1515
// pure functions, don't alter but passes on `gd` and parts of `trace` without deep copying
1616
module.exports = function calc(gd, trace) {
17-
var cellsValues = trace.cells.values;
17+
var cellsValues = squareStringMatrix(trace.cells.values);
1818
var slicer = function(a) {
1919
return a.slice(trace.header.values.length, a.length);
2020
};
21-
var headerValues = trace.header.values.map(function(c) {
22-
return Array.isArray(c) ? c : [c];
23-
}).concat(slicer(cellsValues).map(function() {return [''];}));
21+
var headerValuesIn = squareStringMatrix(trace.header.values);
22+
if(headerValuesIn.length && !headerValuesIn[0].length) {
23+
headerValuesIn[0] = [''];
24+
headerValuesIn = squareStringMatrix(headerValuesIn);
25+
}
26+
var headerValues = headerValuesIn
27+
.concat(slicer(cellsValues).map(function() {
28+
return emptyStrings((headerValuesIn[0] || ['']).length);
29+
}));
30+
2431
var domain = trace.domain;
2532
var groupWidth = Math.floor(gd._fullLayout._size.w * (domain.x[1] - domain.x[0]));
2633
var groupHeight = Math.floor(gd._fullLayout._size.h * (domain.y[1] - domain.y[0]));
27-
var headerRowHeights = trace.header.values.length ? headerValues[0].map(function() {return trace.header.height;}) : [c.emptyHeaderHeight];
28-
var rowHeights = cellsValues.length ? cellsValues[0].map(function() {return trace.cells.height;}) : [];
29-
var headerHeight = headerRowHeights.reduce(function(a, b) {return a + b;}, 0);
34+
var headerRowHeights = trace.header.values.length ?
35+
headerValues[0].map(function() { return trace.header.height; }) :
36+
[c.emptyHeaderHeight];
37+
var rowHeights = cellsValues.length ? cellsValues[0].map(function() { return trace.cells.height; }) : [];
38+
var headerHeight = headerRowHeights.reduce(sum, 0);
3039
var scrollHeight = groupHeight - headerHeight;
3140
var minimumFillHeight = scrollHeight + c.uplift;
3241
var anchorToRowBlock = makeAnchorToRowBlock(rowHeights, minimumFillHeight);
@@ -41,24 +50,27 @@ module.exports = function calc(gd, trace) {
4150
trace.columnwidth;
4251
return isNumeric(value) ? Number(value) : 1;
4352
});
44-
var totalColumnWidths = columnWidths.reduce(function(p, n) {return p + n;}, 0);
53+
var totalColumnWidths = columnWidths.reduce(sum, 0);
4554

4655
// fit columns in the available vertical space as there's no vertical scrolling now
47-
columnWidths = columnWidths.map(function(d) {return d / totalColumnWidths * groupWidth;});
56+
columnWidths = columnWidths.map(function(d) { return d / totalColumnWidths * groupWidth; });
57+
58+
var maxLineWidth = Math.max(arrayMax(trace.header.line.width), arrayMax(trace.cells.line.width));
4859

4960
var calcdata = {
5061
key: trace.index,
5162
translateX: domain.x[0] * gd._fullLayout._size.w,
5263
translateY: gd._fullLayout._size.h * (1 - domain.y[1]),
5364
size: gd._fullLayout._size,
5465
width: groupWidth,
66+
maxLineWidth: maxLineWidth,
5567
height: groupHeight,
5668
columnOrder: columnOrder, // will be mutated on column move, todo use in callback
5769
groupHeight: groupHeight,
5870
rowBlocks: rowBlocks,
5971
headerRowBlocks: headerRowBlocks,
6072
scrollY: 0, // will be mutated on scroll
61-
cells: trace.cells,
73+
cells: extendFlat({}, trace.cells, {values: cellsValues}),
6274
headerCells: extendFlat({}, trace.header, {values: headerValues}),
6375
gdColumns: headerValues.map(function(d) {return d[0];}),
6476
gdColumnsOriginalOrder: headerValues.map(function(d) {return d[0];}),
@@ -89,6 +101,47 @@ module.exports = function calc(gd, trace) {
89101
return calcdata;
90102
};
91103

104+
function arrayMax(maybeArray) {
105+
if(Array.isArray(maybeArray)) {
106+
var max = 0;
107+
for(var i = 0; i < maybeArray.length; i++) {
108+
max = Math.max(max, arrayMax(maybeArray[i]));
109+
}
110+
return max;
111+
}
112+
return maybeArray;
113+
}
114+
115+
function sum(a, b) { return a + b; }
116+
117+
// fill matrix in place to equal lengths
118+
// and ensure it's uniformly 2D
119+
function squareStringMatrix(matrixIn) {
120+
var matrix = matrixIn.slice();
121+
var minLen = Infinity;
122+
var maxLen = 0;
123+
var i;
124+
for(i = 0; i < matrix.length; i++) {
125+
if(!Array.isArray(matrix[i])) matrix[i] = [matrix[i]];
126+
minLen = Math.min(minLen, matrix[i].length);
127+
maxLen = Math.max(maxLen, matrix[i].length);
128+
}
129+
130+
if(minLen !== maxLen) {
131+
for(i = 0; i < matrix.length; i++) {
132+
var padLen = maxLen - matrix[i].length;
133+
if(padLen) matrix[i] = matrix[i].concat(emptyStrings(padLen));
134+
}
135+
}
136+
return matrix;
137+
}
138+
139+
function emptyStrings(len) {
140+
var padArray = new Array(len);
141+
for(var j = 0; j < len; j++) padArray[j] = '';
142+
return padArray;
143+
}
144+
92145
function xScale(d) {
93146
return d.calcdata.columns.reduce(function(prev, next) {
94147
return next.xIndex < d.xIndex ? prev + next.columnWidth : prev;

src/traces/table/plot.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,18 @@ module.exports = function plot(gd, wrappedTraceHolders) {
221221
.attr('fill', 'none');
222222

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

227229
updateBlockYPosition(null, cellsColumnBlock, tableControlView);
228230
};
229231

232+
function roundHalfWidth(d) {
233+
return Math.ceil(d.calcdata.maxLineWidth / 2);
234+
}
235+
230236
function scrollAreaBottomClipKey(gd, d) {
231237
return 'clip' + gd._fullLayout._uid + '_scrollAreaBottomClip_' + d.key;
232238
}
146 Bytes
Loading
-73.8 KB
Binary file not shown.
Loading
-1.01 KB
Loading

test/image/baselines/table_ragged.png

6.96 KB
Loading
-585 Bytes
Loading

test/image/mocks/table_latex_multitrace.json

-137
This file was deleted.

test/image/mocks/table_latex_multitrace_scatter.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"header": {
2626
"values": [["<b>#</b>"], ["Half-angle form"], ["Equivalent"]],
2727
"align": ["right", "center", "center"],
28-
"line": {"width": 0.0},
28+
"line": {"width": 1, "color": ["dimgray", "grey"]},
2929
"fill": {"color": ["dimgray", "grey"]},
3030
"font": {"family": "Arial", "size": 14, "color": "white"}
3131
},
@@ -55,7 +55,7 @@
5555
"header": {
5656
"values": [["<b>#</b>"], ["$$\\theta$$"], ["Half-angle sine"], ["Half-angle cosine"]],
5757
"align": "right",
58-
"line": {"width": 0.0},
58+
"line": {"width": 1, "color": ["dimgray", "grey"]},
5959
"fill": {"color": ["dimgray", "grey"]},
6060
"font": {"family": "Arial", "size": 14, "color": "white"}
6161
},
@@ -88,7 +88,7 @@
8888
"header": {
8989
"values": [["Trig function"], ["Double-angle form"]],
9090
"align": ["right", "left"],
91-
"line": {"width": 0.0},
91+
"line": {"width": 1, "color": ["dimgray", "grey"]},
9292
"fill": {"color": ["dimgray", "grey"]},
9393
"font": {"family": "Arial", "size": 14, "color": "white"}
9494
},
@@ -117,7 +117,7 @@
117117
"header": {
118118
"values": [["<b>#</b>"], ["$$\\theta$$"], ["Double-angle sine"], ["Double-angle cosine"]],
119119
"align": "right",
120-
"line": {"width": 0.0},
120+
"line": {"width": 1, "color": ["dimgray", "grey"]},
121121
"fill": {"color": ["dimgray", "grey"]},
122122
"font": {"family": "Arial", "size": 14, "color": "white"}
123123
},

test/image/mocks/table_plain_birds.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@
2525

2626
"align": ["right", "left", "right", "right", "left", "left", "left"],
2727

28-
"line": {
29-
"color": "lightgray",
30-
"width": 0.0
31-
},
28+
"line": {"width": 1, "color": ["dimgray", "grey"]},
3229

3330
"fill": {
3431
"color": ["dimgray", "grey"]

test/image/mocks/table_ragged.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"data": [{
3+
"type": "table",
4+
"header": {"values": ["z", ["a","b"], ["c"], []],
5+
"line": {"width": 4}},
6+
"cells": {
7+
"values": [[1],[2,3],4,[],[5]],
8+
"fill": {"color": "#eee"},
9+
"line": {"width": [[1,9],[3,7],[5,5],[7,3],[9,1]]}
10+
}
11+
}],
12+
"layout": {"width": 400, "height": 400}
13+
}

test/image/mocks/table_wrapped_birds.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@
2525

2626
"align": ["right", "left", "right", "right", "left", "left", "left"],
2727

28-
"line": {
29-
"color": "lightgray",
30-
"width": 0.0
31-
},
28+
"line": {"width": 1, "color": ["dimgray", "grey"]},
3229

3330
"fill": {
3431
"color": ["dimgray", "grey"]

test/jasmine/tests/table_test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var createGraphDiv = require('../assets/create_graph_div');
88
var destroyGraphDiv = require('../assets/destroy_graph_div');
99
var supplyAllDefaults = require('../assets/supply_defaults');
1010

11-
var mockMulti = require('@mocks/table_latex_multitrace.json');
11+
var mockMulti = require('@mocks/table_latex_multitrace_scatter.json');
1212

1313
// mock with two columns; lowest column count of general case
1414
var mock2 = Lib.extendDeep({}, mockMulti);
@@ -305,7 +305,7 @@ describe('table', function() {
305305
expect(gd.data[0].header.fill.color).toEqual('magenta');
306306
expect(gd.data[0].header.values.length).toEqual(7);
307307
expect(gd.data[0].cells.values.length).toEqual(7);
308-
expect(gd.data[0].header.line.color).toEqual('lightgray'); // no change relative to original mock value
308+
expect(gd.data[0].header.line.color).toEqual(['dimgray', 'grey']); // no change relative to original mock value
309309
expect(gd.data[0].cells.line.color).toEqual(['grey']); // no change relative to original mock value
310310

311311
done();

0 commit comments

Comments
 (0)