@malloydata/render 0.0.58-dev230714185813 → 0.0.58-dev230715003933
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html/table.d.ts +9 -1
- package/dist/html/table.js +219 -52
- package/package.json +2 -2
package/dist/html/table.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { DataColumn } from '@malloydata/malloy';
|
|
1
|
+
import { DataColumn, Field, Tags } from '@malloydata/malloy';
|
|
2
2
|
import { StyleDefaults } from '../data_styles';
|
|
3
3
|
import { ContainerRenderer } from './container';
|
|
4
|
+
import { Renderer } from '../renderer';
|
|
4
5
|
export declare class HTMLTableRenderer extends ContainerRenderer {
|
|
5
6
|
protected childrenStyleDefaults: StyleDefaults;
|
|
6
7
|
render(table: DataColumn): Promise<HTMLElement>;
|
|
8
|
+
generatePivotedCells(table: DataColumn, shouldTranspose: boolean): Promise<Map<string, Map<string, HTMLTableCellElement>>>;
|
|
9
|
+
generateNoValueCell(table: DataColumn, field: Field, shouldTranspose: boolean): HTMLTableCellElement;
|
|
10
|
+
tagIsPresent(tags: Tags | undefined, tag: string): boolean;
|
|
11
|
+
createCellAndRender(childRenderer: Renderer, value: DataColumn, shouldTranspose: boolean): Promise<HTMLTableCellElement>;
|
|
12
|
+
createHeaderCell(field: Field, shouldTranspose: boolean): HTMLTableCellElement;
|
|
13
|
+
createCell(childRenderer: Renderer, shouldTranspose: boolean): HTMLTableCellElement;
|
|
14
|
+
renderChild(value: DataColumn): Promise<HTMLElement>;
|
|
7
15
|
}
|
package/dist/html/table.js
CHANGED
|
@@ -28,6 +28,25 @@ const container_1 = require("./container");
|
|
|
28
28
|
const number_1 = require("./number");
|
|
29
29
|
const utils_1 = require("./utils");
|
|
30
30
|
const tags_utils_1 = require("../tags_utils");
|
|
31
|
+
class PivotedField {
|
|
32
|
+
constructor(parentField, values) {
|
|
33
|
+
this.parentField = parentField;
|
|
34
|
+
this.values = values;
|
|
35
|
+
this.key = JSON.stringify({
|
|
36
|
+
parentField: this.parentField.name,
|
|
37
|
+
values: this.values.filter(v => (v.isScalar() ? v.key : '')),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
class PivotedColumnField {
|
|
42
|
+
constructor(pivotedField, field) {
|
|
43
|
+
this.pivotedField = pivotedField;
|
|
44
|
+
this.field = field;
|
|
45
|
+
}
|
|
46
|
+
isPivotedColumnField() {
|
|
47
|
+
return this instanceof PivotedColumnField;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
31
50
|
class HTMLTableRenderer extends container_1.ContainerRenderer {
|
|
32
51
|
constructor() {
|
|
33
52
|
super(...arguments);
|
|
@@ -36,37 +55,69 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
|
|
|
36
55
|
};
|
|
37
56
|
}
|
|
38
57
|
async render(table) {
|
|
58
|
+
var _a;
|
|
39
59
|
if (!table.isArray() && !table.isRecord()) {
|
|
40
60
|
throw new Error('Invalid type for Table Renderer');
|
|
41
61
|
}
|
|
42
|
-
const shouldTranspose = this.tags
|
|
43
|
-
? this.tags.getMalloyTags().properties['transpose'] === true
|
|
44
|
-
: false;
|
|
62
|
+
const shouldTranspose = this.tagIsPresent(this.tags, 'transpose');
|
|
45
63
|
if (shouldTranspose && table.field.intrinsicFields.length > 20) {
|
|
46
64
|
throw new Error('Transpose limit of 20 columns exceeded.');
|
|
47
65
|
}
|
|
48
66
|
let rowIndex = 0;
|
|
49
67
|
let columnIndex = 0;
|
|
50
|
-
|
|
68
|
+
let cells = [];
|
|
51
69
|
cells[rowIndex] = [];
|
|
70
|
+
const columnFields = [];
|
|
71
|
+
let pivotDepth = 0;
|
|
52
72
|
for (const field of table.field.intrinsicFields) {
|
|
53
73
|
if ((0, tags_utils_1.isFieldHidden)(field)) {
|
|
54
74
|
continue;
|
|
55
75
|
}
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
const childRenderer = this.childRenderers[field.name];
|
|
77
|
+
const shouldPivot = childRenderer instanceof HTMLTableRenderer &&
|
|
78
|
+
this.tagIsPresent(childRenderer.tags, 'pivot');
|
|
79
|
+
if (shouldPivot) {
|
|
80
|
+
const pivotedFields = new Map();
|
|
81
|
+
let dimensions = undefined;
|
|
82
|
+
let nonDimensions = [];
|
|
83
|
+
for (const row of table) {
|
|
84
|
+
const dc = row.cell(field);
|
|
85
|
+
if (!dc.isArray() && !dc.isRecord()) {
|
|
86
|
+
throw new Error(`Cannot pivot field ${field.name}.`);
|
|
87
|
+
}
|
|
88
|
+
if (!dimensions) {
|
|
89
|
+
const fieldi = dc.field;
|
|
90
|
+
dimensions = fieldi.dimensions;
|
|
91
|
+
nonDimensions = fieldi.allFields.filter(f => dimensions.indexOf(f) < 0);
|
|
92
|
+
if (nonDimensions.length === 0) {
|
|
93
|
+
throw new Error(`Can not pivot ${field.name} since all of its fields are dimensions.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const innerRow of dc) {
|
|
97
|
+
const pivotedField = new PivotedField(field, dimensions.map(d => innerRow.cell(d)));
|
|
98
|
+
const pfKey = pivotedField.key;
|
|
99
|
+
if (!pivotedFields[pfKey]) {
|
|
100
|
+
pivotedFields.set(pfKey, pivotedField);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const pf of pivotedFields) {
|
|
105
|
+
for (const nonDimension of nonDimensions) {
|
|
106
|
+
columnFields.push(new PivotedColumnField(pf[1], nonDimension));
|
|
107
|
+
cells[rowIndex][columnIndex] = childRenderer.createHeaderCell(nonDimension, shouldTranspose);
|
|
108
|
+
columnIndex++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
pivotDepth = Math.max(pivotDepth, dimensions.length);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
cells[rowIndex][columnIndex] = this.createHeaderCell(field, shouldTranspose);
|
|
115
|
+
columnFields.push(field);
|
|
116
|
+
columnIndex++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (pivotDepth > 0 && columnFields.length > 30) {
|
|
120
|
+
throw new Error('Pivot limit of 30 columns exceeded.');
|
|
70
121
|
}
|
|
71
122
|
if (!shouldTranspose && this.options.isDrillingEnabled) {
|
|
72
123
|
const drillHeader = this.document.createElement('th');
|
|
@@ -80,55 +131,109 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
|
|
|
80
131
|
columnIndex++;
|
|
81
132
|
}
|
|
82
133
|
rowIndex++;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
const pivotHeaderCells = [];
|
|
135
|
+
for (let r = 0; r < pivotDepth; r++) {
|
|
136
|
+
const row = [];
|
|
137
|
+
let lastPivotedColumnHash = null;
|
|
138
|
+
for (const field of columnFields) {
|
|
139
|
+
if (field instanceof PivotedColumnField) {
|
|
140
|
+
const headerCell = this.document.createElement('th');
|
|
141
|
+
headerCell.style.cssText = `
|
|
142
|
+
padding: 8px;
|
|
143
|
+
color: var(--malloy-title-color, #505050);
|
|
144
|
+
border-bottom: 1px solid var(--malloy-border-color, #eaeaea);
|
|
145
|
+
`;
|
|
146
|
+
const pfKey = field.pivotedField.key;
|
|
147
|
+
const valueIndex = field.pivotedField.values.length - pivotDepth + r;
|
|
148
|
+
if (lastPivotedColumnHash !== pfKey && valueIndex >= 0) {
|
|
149
|
+
const value = field.pivotedField.values[valueIndex];
|
|
150
|
+
headerCell.appendChild(await this.childRenderers[field.pivotedField.parentField.name].renderChild(value));
|
|
151
|
+
lastPivotedColumnHash = pfKey;
|
|
152
|
+
}
|
|
153
|
+
row.push(headerCell);
|
|
89
154
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
cellElement.style.cssText = `
|
|
96
|
-
padding: ${childRenderer instanceof HTMLTableRenderer ? '0' : '8px'};
|
|
97
|
-
vertical-align: top;
|
|
155
|
+
else {
|
|
156
|
+
const headerCell = this.document.createElement('th');
|
|
157
|
+
headerCell.style.cssText = `
|
|
158
|
+
padding: 8px;
|
|
159
|
+
color: var(--malloy-title-color, #505050);
|
|
98
160
|
border-bottom: 1px solid var(--malloy-border-color, #eaeaea);
|
|
99
|
-
${isNumeric || shouldTranspose ? 'text-align: right;' : ''}
|
|
100
161
|
`;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
columnIndex++;
|
|
162
|
+
row.push(headerCell);
|
|
163
|
+
}
|
|
104
164
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
165
|
+
pivotHeaderCells.push(row);
|
|
166
|
+
rowIndex++;
|
|
167
|
+
}
|
|
168
|
+
cells = [...pivotHeaderCells, ...cells];
|
|
169
|
+
try {
|
|
170
|
+
for (const row of table) {
|
|
171
|
+
cells[rowIndex] = [];
|
|
172
|
+
columnIndex = 0;
|
|
173
|
+
let currentPivotedFieldKey = '';
|
|
174
|
+
let pivotedCells = new Map();
|
|
175
|
+
for (const field of columnFields) {
|
|
176
|
+
if (field instanceof PivotedColumnField) {
|
|
177
|
+
const childRenderer = this.childRenderers[field.pivotedField.parentField.name];
|
|
178
|
+
const childTableRecord = row.cell(field.pivotedField.parentField);
|
|
179
|
+
await (0, utils_1.yieldTask)();
|
|
180
|
+
if (field.pivotedField.key !== currentPivotedFieldKey) {
|
|
181
|
+
pivotedCells = await childRenderer.generatePivotedCells(childTableRecord, shouldTranspose);
|
|
182
|
+
currentPivotedFieldKey = field.pivotedField.key;
|
|
183
|
+
}
|
|
184
|
+
const pfKey = field.pivotedField.key;
|
|
185
|
+
if (pivotedCells.has(pfKey) &&
|
|
186
|
+
((_a = pivotedCells.get(pfKey)) === null || _a === void 0 ? void 0 : _a.has(field.field.name))) {
|
|
187
|
+
cells[rowIndex][columnIndex] = pivotedCells
|
|
188
|
+
.get(pfKey)
|
|
189
|
+
.get(field.field.name);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
cells[rowIndex][columnIndex] = childRenderer.generateNoValueCell(childTableRecord, field.field, shouldTranspose);
|
|
193
|
+
}
|
|
194
|
+
columnIndex++;
|
|
195
|
+
// back
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if ((0, tags_utils_1.isFieldHidden)(field)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const childRenderer = this.childRenderers[field.name];
|
|
202
|
+
cells[rowIndex][columnIndex] = await this.createCellAndRender(childRenderer, row.cell(field), shouldTranspose);
|
|
203
|
+
columnIndex++;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// TODO(figutierrez): Deal with drill when transpose is on.
|
|
207
|
+
if (!shouldTranspose && this.options.isDrillingEnabled) {
|
|
208
|
+
const drillCell = this.document.createElement('td');
|
|
209
|
+
const drillIcon = (0, utils_1.createDrillIcon)(this.document);
|
|
210
|
+
drillCell.appendChild(drillIcon);
|
|
211
|
+
drillCell.style.cssText = `
|
|
111
212
|
padding: 8px;
|
|
112
213
|
vertical-align: top;
|
|
113
214
|
border-bottom: 1px solid var(--malloy-border-color, #eaeaea);
|
|
114
215
|
width: 25px;
|
|
115
216
|
cursor: pointer
|
|
116
217
|
`;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
218
|
+
drillCell.onclick = () => {
|
|
219
|
+
if (this.options.onDrill) {
|
|
220
|
+
const { drillQuery, drillFilters } = (0, drill_1.getDrillQuery)(row);
|
|
221
|
+
this.options.onDrill(drillQuery, drillIcon, drillFilters);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
cells[rowIndex][columnIndex] = drillCell;
|
|
225
|
+
columnIndex++;
|
|
226
|
+
}
|
|
227
|
+
rowIndex++;
|
|
125
228
|
}
|
|
126
|
-
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
throw new Error(`${e.toString()} ${e.stack}`);
|
|
127
232
|
}
|
|
128
233
|
const tableElement = this.document.createElement('table');
|
|
129
234
|
let tableSection = this.document.createElement('thead');
|
|
130
235
|
for (let row = 0; row < (shouldTranspose ? columnIndex : rowIndex); row++) {
|
|
131
|
-
if (row === 1) {
|
|
236
|
+
if (row === 1 + pivotDepth) {
|
|
132
237
|
tableElement.appendChild(tableSection);
|
|
133
238
|
tableSection = this.document.createElement('tbody');
|
|
134
239
|
}
|
|
@@ -148,6 +253,68 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
|
|
|
148
253
|
`;
|
|
149
254
|
return tableElement;
|
|
150
255
|
}
|
|
256
|
+
async generatePivotedCells(table, shouldTranspose) {
|
|
257
|
+
const result = new Map();
|
|
258
|
+
if (!table.isArray() && !table.isRecord()) {
|
|
259
|
+
throw new Error(`Could not pivot ${table.field.name}`);
|
|
260
|
+
}
|
|
261
|
+
const dimensions = table.field.dimensions;
|
|
262
|
+
for (const row of table) {
|
|
263
|
+
const pf = new PivotedField(table.field, dimensions.map(f => row.cell(f.name)));
|
|
264
|
+
const renderedCells = new Map();
|
|
265
|
+
for (const nonDimension of table.field.allFields.filter(f => dimensions.indexOf(f) < 0)) {
|
|
266
|
+
const childRenderer = this.childRenderers[nonDimension.name];
|
|
267
|
+
renderedCells.set(nonDimension.name, await this.createCellAndRender(childRenderer, row.cell(nonDimension.name), shouldTranspose));
|
|
268
|
+
}
|
|
269
|
+
result.set(pf.key, renderedCells);
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
generateNoValueCell(table, field, shouldTranspose) {
|
|
274
|
+
if (!table.isArray() && !table.isRecord()) {
|
|
275
|
+
throw new Error(`Could not pivot ${table.field.name}`);
|
|
276
|
+
}
|
|
277
|
+
const cell = this.createCell(this.childRenderers[field.name], shouldTranspose);
|
|
278
|
+
cell.innerHTML = '-';
|
|
279
|
+
return cell;
|
|
280
|
+
}
|
|
281
|
+
tagIsPresent(tags, tag) {
|
|
282
|
+
return tags ? tags.getMalloyTags().properties[tag] === true : false;
|
|
283
|
+
}
|
|
284
|
+
async createCellAndRender(childRenderer, value, shouldTranspose) {
|
|
285
|
+
const cell = this.createCell(childRenderer, shouldTranspose);
|
|
286
|
+
cell.appendChild(await childRenderer.render(value));
|
|
287
|
+
return cell;
|
|
288
|
+
}
|
|
289
|
+
createHeaderCell(field, shouldTranspose) {
|
|
290
|
+
let name = (0, utils_1.formatTitle)(this.options, field, this.options.dataStyles[field.name], field.parentExplore.queryTimezone);
|
|
291
|
+
const isNumeric = this.childRenderers[field.name] instanceof number_1.HTMLNumberRenderer;
|
|
292
|
+
const headerCell = this.document.createElement('th');
|
|
293
|
+
headerCell.style.cssText = `
|
|
294
|
+
padding: 8px;
|
|
295
|
+
color: var(--malloy-title-color, #505050);
|
|
296
|
+
border-bottom: 1px solid var(--malloy-border-color, #eaeaea);
|
|
297
|
+
text-align: ${isNumeric || shouldTranspose ? 'right' : 'left'};
|
|
298
|
+
`;
|
|
299
|
+
name = name.replace(/_/g, '_​');
|
|
300
|
+
headerCell.innerHTML = name;
|
|
301
|
+
return headerCell;
|
|
302
|
+
}
|
|
303
|
+
createCell(childRenderer, shouldTranspose) {
|
|
304
|
+
const isNumeric = childRenderer instanceof number_1.HTMLNumberRenderer;
|
|
305
|
+
(0, utils_1.yieldTask)();
|
|
306
|
+
const cellElement = this.document.createElement('td');
|
|
307
|
+
cellElement.style.cssText = `
|
|
308
|
+
padding: ${childRenderer instanceof HTMLTableRenderer ? '0' : '8px'};
|
|
309
|
+
vertical-align: top;
|
|
310
|
+
border-bottom: 1px solid var(--malloy-border-color, #eaeaea);
|
|
311
|
+
${isNumeric || shouldTranspose ? 'text-align: right;' : ''}
|
|
312
|
+
`;
|
|
313
|
+
return cellElement;
|
|
314
|
+
}
|
|
315
|
+
async renderChild(value) {
|
|
316
|
+
return this.childRenderers[value.field.name].render(value);
|
|
317
|
+
}
|
|
151
318
|
}
|
|
152
319
|
exports.HTMLTableRenderer = HTMLTableRenderer;
|
|
153
320
|
//# sourceMappingURL=table.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/render",
|
|
3
|
-
"version": "0.0.58-
|
|
3
|
+
"version": "0.0.58-dev230715003933",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"prepublishOnly": "npm run build"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@malloydata/malloy": "^0.0.58-
|
|
21
|
+
"@malloydata/malloy": "^0.0.58-dev230715003933",
|
|
22
22
|
"@types/luxon": "^2.4.0",
|
|
23
23
|
"lodash": "^4.17.20",
|
|
24
24
|
"luxon": "^2.4.0",
|