@malloydata/render 0.0.58 → 0.0.59-dev230718005248

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.
@@ -1,13 +1,16 @@
1
- import { DataColumn, Field, Tags } from '@malloydata/malloy';
1
+ import { DataColumn, Field } from '@malloydata/malloy';
2
2
  import { StyleDefaults } from '../data_styles';
3
3
  import { ContainerRenderer } from './container';
4
4
  import { Renderer } from '../renderer';
5
5
  export declare class HTMLTableRenderer extends ContainerRenderer {
6
6
  protected childrenStyleDefaults: StyleDefaults;
7
7
  render(table: DataColumn): Promise<HTMLElement>;
8
+ calculatePivotDimensions(table: DataColumn): {
9
+ dimensions: Field[];
10
+ nonDimensions: Field[];
11
+ };
8
12
  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;
13
+ generateNoValueCell(field: Field, shouldTranspose: boolean): HTMLTableCellElement;
11
14
  createCellAndRender(childRenderer: Renderer, value: DataColumn, shouldTranspose: boolean): Promise<HTMLTableCellElement>;
12
15
  createHeaderCell(field: Field, shouldTranspose: boolean): HTMLTableCellElement;
13
16
  createCell(childRenderer: Renderer, shouldTranspose: boolean): HTMLTableCellElement;
@@ -59,7 +59,7 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
59
59
  if (!table.isArray() && !table.isRecord()) {
60
60
  throw new Error('Invalid type for Table Renderer');
61
61
  }
62
- const shouldTranspose = this.tagIsPresent(this.tags, 'transpose');
62
+ const shouldTranspose = (0, utils_1.tagIsPresent)(this.tags, 'transpose');
63
63
  if (shouldTranspose && table.field.intrinsicFields.length > 20) {
64
64
  throw new Error('Transpose limit of 20 columns exceeded.');
65
65
  }
@@ -75,23 +75,23 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
75
75
  }
76
76
  const childRenderer = this.childRenderers[field.name];
77
77
  const shouldPivot = childRenderer instanceof HTMLTableRenderer &&
78
- this.tagIsPresent(childRenderer.tags, 'pivot');
78
+ (0, utils_1.tagIsPresent)(childRenderer.tags, 'pivot');
79
79
  if (shouldPivot) {
80
80
  const pivotedFields = new Map();
81
81
  let dimensions = undefined;
82
82
  let nonDimensions = [];
83
83
  for (const row of table) {
84
84
  const dc = row.cell(field);
85
+ if (dc.isNull()) {
86
+ continue;
87
+ }
85
88
  if (!dc.isArray() && !dc.isRecord()) {
86
- throw new Error(`Cannot pivot field ${field.name}.`);
89
+ throw new Error(`Can not pivot field ${field.name}.`);
87
90
  }
88
91
  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
- }
92
+ const dimensionsResult = childRenderer.calculatePivotDimensions(dc);
93
+ dimensions = dimensionsResult.dimensions;
94
+ nonDimensions = dimensionsResult.nonDimensions;
95
95
  }
96
96
  for (const innerRow of dc) {
97
97
  const pivotedField = new PivotedField(field, dimensions.map(d => innerRow.cell(d)));
@@ -101,6 +101,9 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
101
101
  }
102
102
  }
103
103
  }
104
+ if (!dimensions) {
105
+ throw new Error(`Could not pivot ${field.name}, no data found.`);
106
+ }
104
107
  for (const pf of pivotedFields) {
105
108
  for (const nonDimension of nonDimensions) {
106
109
  columnFields.push(new PivotedColumnField(pf[1], nonDimension));
@@ -166,69 +169,64 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
166
169
  rowIndex++;
167
170
  }
168
171
  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
172
+ for (const row of table) {
173
+ cells[rowIndex] = [];
174
+ columnIndex = 0;
175
+ let currentPivotedFieldKey = '';
176
+ let pivotedCells = new Map();
177
+ for (const field of columnFields) {
178
+ if (field instanceof PivotedColumnField) {
179
+ const childRenderer = this.childRenderers[field.pivotedField.parentField.name];
180
+ const childTableRecord = row.cell(field.pivotedField.parentField);
181
+ await (0, utils_1.yieldTask)();
182
+ if (field.pivotedField.key !== currentPivotedFieldKey) {
183
+ pivotedCells = await childRenderer.generatePivotedCells(childTableRecord, shouldTranspose);
184
+ currentPivotedFieldKey = field.pivotedField.key;
185
+ }
186
+ const pfKey = field.pivotedField.key;
187
+ if (pivotedCells.has(pfKey) &&
188
+ ((_a = pivotedCells.get(pfKey)) === null || _a === void 0 ? void 0 : _a.has(field.field.name))) {
189
+ cells[rowIndex][columnIndex] = pivotedCells
190
+ .get(pfKey)
191
+ .get(field.field.name);
196
192
  }
197
193
  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++;
194
+ cells[rowIndex][columnIndex] = childRenderer.generateNoValueCell(field.field, shouldTranspose);
204
195
  }
196
+ columnIndex++;
197
+ // back
205
198
  }
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 = `
199
+ else {
200
+ if ((0, tags_utils_1.isFieldHidden)(field)) {
201
+ continue;
202
+ }
203
+ const childRenderer = this.childRenderers[field.name];
204
+ cells[rowIndex][columnIndex] = await this.createCellAndRender(childRenderer, row.cell(field), shouldTranspose);
205
+ columnIndex++;
206
+ }
207
+ }
208
+ // TODO(figutierrez): Deal with drill when transpose is on.
209
+ if (!shouldTranspose && this.options.isDrillingEnabled) {
210
+ const drillCell = this.document.createElement('td');
211
+ const drillIcon = (0, utils_1.createDrillIcon)(this.document);
212
+ drillCell.appendChild(drillIcon);
213
+ drillCell.style.cssText = `
212
214
  padding: 8px;
213
215
  vertical-align: top;
214
216
  border-bottom: 1px solid var(--malloy-border-color, #eaeaea);
215
217
  width: 25px;
216
218
  cursor: pointer
217
219
  `;
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++;
220
+ drillCell.onclick = () => {
221
+ if (this.options.onDrill) {
222
+ const { drillQuery, drillFilters } = (0, drill_1.getDrillQuery)(row);
223
+ this.options.onDrill(drillQuery, drillIcon, drillFilters);
224
+ }
225
+ };
226
+ cells[rowIndex][columnIndex] = drillCell;
227
+ columnIndex++;
228
228
  }
229
- }
230
- catch (e) {
231
- throw new Error(`${e.toString()} ${e.stack}`);
229
+ rowIndex++;
232
230
  }
233
231
  const tableElement = this.document.createElement('table');
234
232
  let tableSection = this.document.createElement('thead');
@@ -253,12 +251,40 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
253
251
  `;
254
252
  return tableElement;
255
253
  }
254
+ calculatePivotDimensions(table) {
255
+ if (!table.isArray() && !table.isRecord()) {
256
+ throw new Error(`Could not pivot ${table.field.name}`);
257
+ }
258
+ let dimensions = undefined;
259
+ const userSpecifiedDimensions = (0, utils_1.parseCommaSeparatedParameterTagValue)(this.tags, 'pivotDimensions');
260
+ if (userSpecifiedDimensions) {
261
+ dimensions = table.field.allFields.filter(f => userSpecifiedDimensions.indexOf(f.name) >= 0);
262
+ if (dimensions.length !== userSpecifiedDimensions.length) {
263
+ for (const dim of userSpecifiedDimensions) {
264
+ if (table.field.allFields.filter(f => f.name === dim).length === 0) {
265
+ throw new Error(`Could not pivot ${table.field.name} since ${dim} is not a valid field.`);
266
+ }
267
+ }
268
+ }
269
+ }
270
+ else {
271
+ dimensions = table.field.dimensions;
272
+ }
273
+ const nonDimensions = table.field.allFields.filter(f => dimensions.indexOf(f) < 0);
274
+ if (nonDimensions.length === 0) {
275
+ throw new Error(`Can not pivot ${table.field.name} since all of its fields are dimensions.`);
276
+ }
277
+ return { dimensions, nonDimensions };
278
+ }
256
279
  async generatePivotedCells(table, shouldTranspose) {
257
280
  const result = new Map();
281
+ if (table.isNull()) {
282
+ return result;
283
+ }
258
284
  if (!table.isArray() && !table.isRecord()) {
259
285
  throw new Error(`Could not pivot ${table.field.name}`);
260
286
  }
261
- const dimensions = table.field.dimensions;
287
+ const { dimensions } = this.calculatePivotDimensions(table);
262
288
  for (const row of table) {
263
289
  const pf = new PivotedField(table.field, dimensions.map(f => row.cell(f.name)));
264
290
  const renderedCells = new Map();
@@ -266,21 +292,18 @@ class HTMLTableRenderer extends container_1.ContainerRenderer {
266
292
  const childRenderer = this.childRenderers[nonDimension.name];
267
293
  renderedCells.set(nonDimension.name, await this.createCellAndRender(childRenderer, row.cell(nonDimension.name), shouldTranspose));
268
294
  }
295
+ if (result.has(pf.key)) {
296
+ throw new Error(`Can not pivot ${table.field.name} dimensions lead to non unique pivots.`);
297
+ }
269
298
  result.set(pf.key, renderedCells);
270
299
  }
271
300
  return result;
272
301
  }
273
- generateNoValueCell(table, field, shouldTranspose) {
274
- if (!table.isArray() && !table.isRecord()) {
275
- throw new Error(`Could not pivot ${table.field.name}`);
276
- }
302
+ generateNoValueCell(field, shouldTranspose) {
277
303
  const cell = this.createCell(this.childRenderers[field.name], shouldTranspose);
278
304
  cell.innerHTML = '-';
279
305
  return cell;
280
306
  }
281
- tagIsPresent(tags, tag) {
282
- return tags ? tags.getMalloyTags().properties[tag] === true : false;
283
- }
284
307
  async createCellAndRender(childRenderer, value, shouldTranspose) {
285
308
  const cell = this.createCell(childRenderer, shouldTranspose);
286
309
  cell.appendChild(await childRenderer.render(value));
@@ -12,3 +12,5 @@ export declare function createNullElement(document: Document): HTMLElement;
12
12
  export declare function createDrillIcon(document: Document): HTMLElement;
13
13
  export declare function formatTitle(options: RendererOptions, field: Field, renderDef?: RenderDef | undefined, timezone?: string): string;
14
14
  export declare function tagIsPresent(tags: Tags | undefined, tagToCheck: string): boolean;
15
+ export declare function parameterTagValue(tags: Tags | undefined, tagToGetValue: string): string | undefined;
16
+ export declare function parseCommaSeparatedParameterTagValue(tags: Tags | undefined, tagToGetValue: string): string[] | undefined;
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
26
  };
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.tagIsPresent = exports.formatTitle = exports.createDrillIcon = exports.createNullElement = exports.createErrorElement = exports.yieldTask = exports.normalizeToTimezone = exports.timeToString = exports.getColorScale = void 0;
28
+ exports.parseCommaSeparatedParameterTagValue = exports.parameterTagValue = exports.tagIsPresent = exports.formatTitle = exports.createDrillIcon = exports.createNullElement = exports.createErrorElement = exports.yieldTask = exports.normalizeToTimezone = exports.timeToString = exports.getColorScale = void 0;
29
29
  const malloy_1 = require("@malloydata/malloy");
30
30
  const startCase_1 = __importDefault(require("lodash/startCase"));
31
31
  const luxon_1 = require("luxon");
@@ -232,4 +232,17 @@ function tagIsPresent(tags, tagToCheck) {
232
232
  return tagProperty !== undefined && tagProperty !== false;
233
233
  }
234
234
  exports.tagIsPresent = tagIsPresent;
235
+ function parameterTagValue(tags, tagToGetValue) {
236
+ const tagProperty = tags === null || tags === void 0 ? void 0 : tags.getMalloyTags().properties[tagToGetValue];
237
+ if (typeof tagProperty === 'string') {
238
+ return tagProperty;
239
+ }
240
+ return undefined;
241
+ }
242
+ exports.parameterTagValue = parameterTagValue;
243
+ function parseCommaSeparatedParameterTagValue(tags, tagToGetValue) {
244
+ var _a;
245
+ return (_a = parameterTagValue(tags, tagToGetValue)) === null || _a === void 0 ? void 0 : _a.split(',');
246
+ }
247
+ exports.parseCommaSeparatedParameterTagValue = parseCommaSeparatedParameterTagValue;
235
248
  //# sourceMappingURL=utils.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.59-dev230718005248",
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.59-dev230718005248",
22
22
  "@types/luxon": "^2.4.0",
23
23
  "lodash": "^4.17.20",
24
24
  "luxon": "^2.4.0",