svg-table 0.0.1 → 0.1.0
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/CHANGES.md +5 -0
- package/README.md +447 -1
- package/{tableData.ts → TableData.ts} +2 -3
- package/TableFormatter.ts +587 -0
- package/{tableStyler.ts → TableStyler.ts} +75 -6
- package/dist/{tableData.d.ts → TableData.d.ts} +2 -2
- package/dist/{tableData.d.ts.map → TableData.d.ts.map} +1 -1
- package/dist/{tableData.js → TableData.js} +10 -10
- package/dist/{tableData.js.map → TableData.js.map} +1 -1
- package/dist/TableFormatter.d.ts +459 -0
- package/dist/TableFormatter.d.ts.map +1 -0
- package/dist/TableFormatter.js +579 -0
- package/dist/TableFormatter.js.map +1 -0
- package/dist/{tableStyler.d.ts → TableStyler.d.ts} +58 -3
- package/dist/TableStyler.d.ts.map +1 -0
- package/dist/{tableStyler.js → TableStyler.js} +90 -31
- package/dist/TableStyler.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/tableData.test.js +15 -15
- package/dist/tableFormatter.test.js +54 -15
- package/dist/tableFormatter.test.js.map +1 -1
- package/dist/tableStyler.test.js +236 -20
- package/dist/tableStyler.test.js.map +1 -1
- package/dist/tableSvg.d.ts +2 -2
- package/dist/tableSvg.js +7 -7
- package/index.ts +13 -12
- package/package.json +2 -2
- package/svg-table-0.0.1.tgz +0 -0
- package/tableData.test.ts +1 -1
- package/tableFormatter.test.ts +48 -4
- package/tableStyler.test.ts +133 -10
- package/tableSvg.ts +3 -3
- package/dist/tableFormatter.d.ts +0 -179
- package/dist/tableFormatter.d.ts.map +0 -1
- package/dist/tableFormatter.js +0 -298
- package/dist/tableFormatter.js.map +0 -1
- package/dist/tableStyler.d.ts.map +0 -1
- package/dist/tableStyler.js.map +0 -1
- package/svg-table-0.0.1-snapshot.tgz +0 -0
- package/tableFormatter.ts +0 -306
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import {DataFrame, type Tag, type TagCoordinate, type TagValue} from "data-frame-ts";
|
|
2
|
+
import {failureResult, type Result, successResult} from "result-fn";
|
|
3
|
+
import {TableData} from "./TableData";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type representing a formatter function
|
|
7
|
+
*/
|
|
8
|
+
export type Formatter<D> = (value: D) => string
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default formatter that converts a {@link value} of type `V` to a string
|
|
12
|
+
* @param value The value to convert
|
|
13
|
+
* @return a string representation of the {@link value}
|
|
14
|
+
*/
|
|
15
|
+
export function defaultFormatter<D>(value: D): string {
|
|
16
|
+
return value === undefined || value === null ? '' : `${value}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Formatting<V> = {
|
|
20
|
+
formatter: Formatter<V>
|
|
21
|
+
priority: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function defaultFormatting<D>(): Formatting<D> {
|
|
25
|
+
return {
|
|
26
|
+
formatter: defaultFormatter<D>,
|
|
27
|
+
priority: 0
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export enum TableFormatterType {
|
|
32
|
+
CELL = "cell-formatter",
|
|
33
|
+
COLUMN = "column-formatter",
|
|
34
|
+
ROW = "row-formatter"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isFormattingTag(tag: Tag<TagValue, TagCoordinate>): boolean {
|
|
38
|
+
return (tag.name === TableFormatterType.COLUMN ||
|
|
39
|
+
tag.name === TableFormatterType.ROW ||
|
|
40
|
+
tag.name === TableFormatterType.CELL) &&
|
|
41
|
+
tag.value.hasOwnProperty("formatter") &&
|
|
42
|
+
tag.value.hasOwnProperty("priority")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Represents a formatter used to apply row, column, and cell formatting to a data table.
|
|
47
|
+
* This class is used to define custom formatters for specific rows, columns, or cells,
|
|
48
|
+
* with support for priority-based selection of formatters.
|
|
49
|
+
*/
|
|
50
|
+
export class TableFormatter<V> {
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param dataFrame A data-frame that will be tagged with the row, column, and
|
|
54
|
+
* cell formatters.
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
private constructor(private readonly dataFrame: DataFrame<V>) {
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Constructs a new {@link TableFormatter} from a {@link TableData} object. Underneath,
|
|
62
|
+
* this factory method merely unwraps the {@link DataFrame} from the {@link TableData}
|
|
63
|
+
* and hands it to the constructor.
|
|
64
|
+
* @param tableData The table data object from which to construct the table formatter
|
|
65
|
+
* @return A new {@link TableFormatter} based on the {@link TableData}
|
|
66
|
+
* @see TableFormatter.fromDataFrame
|
|
67
|
+
*/
|
|
68
|
+
static fromTableData<V>(tableData: TableData<V>): TableFormatter<V> {
|
|
69
|
+
return new TableFormatter<V>(tableData.unwrapDataFrame())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Constructs a new {@link TableFormatter} from a {@link DataFrame} object
|
|
74
|
+
* @param dataFrame The data-frame object from which to construct the table formatter
|
|
75
|
+
* @return A new {@link TableFormatter} based on the {@link DataFrame}
|
|
76
|
+
@see TableFormatter.fromTableData
|
|
77
|
+
*/
|
|
78
|
+
static fromDataFrame<V>(dataFrame: DataFrame<V>): TableFormatter<V> {
|
|
79
|
+
return new TableFormatter<V>(dataFrame.copy())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Formatters convert the column value types to formatted strings. The formatter used to format a given
|
|
84
|
+
* cell depends on the priority of each formatter associated with that cell. The formatter with the
|
|
85
|
+
* highest priority is used. If two or more formatters for a given cell have the same priority, the selected
|
|
86
|
+
* formatter is indeterminant.
|
|
87
|
+
* @param columnIndex The index of the column to which to add the formatter
|
|
88
|
+
* @param formatter The formatter
|
|
89
|
+
* @param [priority = 0] The priority of this formatter. If cells have more than one associated formatter,
|
|
90
|
+
* the one with the highest priority number is used.
|
|
91
|
+
* @see addColumnFormatters
|
|
92
|
+
* @see addRowFormatter
|
|
93
|
+
* @see addRowFormatters
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* // create the data
|
|
97
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
98
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
99
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
100
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
101
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
102
|
+
* ]).getOrThrow()
|
|
103
|
+
*
|
|
104
|
+
* // create the table-data object from the data, and then hand the table-data
|
|
105
|
+
* // to the table formatter, add column formats, and format the table, getting
|
|
106
|
+
* // back a new TableData<string>
|
|
107
|
+
* const tableData: TableData<string> = TableData.fromDataFrame<string | number | Date>(data)
|
|
108
|
+
* // from the table-data, create a table-formatter
|
|
109
|
+
* .flatMap(tableData => createTableFormatterFrom(tableData)
|
|
110
|
+
* // add a column formatter for the first column of dates
|
|
111
|
+
* .addColumnFormatter(0, value => (value as Date).toLocaleDateString())
|
|
112
|
+
* // add a column formatter to the second column of number
|
|
113
|
+
* .flatMap(tf => tf.addColumnFormatter(1, value => defaultFormatter(value)))
|
|
114
|
+
* // add a column formatter to the fourth column of currencies
|
|
115
|
+
* .flatMap(tf => tf.addColumnFormatter(3, value => `$ ${(value as number).toFixed(2)}`))
|
|
116
|
+
* .flatMap(tf => tf.addColumnFormatter(4, value => `${(value as number).toFixed(0)}`))
|
|
117
|
+
* // format the table into a new TableData<string> object
|
|
118
|
+
* .flatMap(tf => tf.formatTable())
|
|
119
|
+
* )
|
|
120
|
+
* .getOrThrow()
|
|
121
|
+
*
|
|
122
|
+
* // we expect the data-frame in the table data to be the following
|
|
123
|
+
* const expectedData = DataFrame.from<string>([
|
|
124
|
+
* ['2/1/2021', '12345', 'gnm-f234', '$ 123.45', '4'],
|
|
125
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
126
|
+
* ['2/3/2021', '34567', 'GNM-H234', '$ 3.65', '40'],
|
|
127
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '9'],
|
|
128
|
+
* ]).getOrThrow()
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
addColumnFormatter(columnIndex: number, formatter: Formatter<V>, priority: number = 0): Result<TableFormatter<V>, string> {
|
|
132
|
+
return this.dataFrame
|
|
133
|
+
.tagColumn<Formatting<V>>(columnIndex, TableFormatterType.COLUMN, {formatter, priority})
|
|
134
|
+
.map(data => new TableFormatter<V>(data))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Adds formatters to specified column indexes in a table formatter.
|
|
139
|
+
*
|
|
140
|
+
* @param columnIndexes An array of column indexes to which the formatter will be applied.
|
|
141
|
+
* @param formatter The formatting function to apply to the specified columns.
|
|
142
|
+
* @param [priority = 0] The priority level for the formatter. Higher priority formatters are applied first.
|
|
143
|
+
* @return A result indicating the success or failure of adding the column formatters.
|
|
144
|
+
* @see addColumnFormatter
|
|
145
|
+
* @see addRowFormatter
|
|
146
|
+
* @see addRowFormatters
|
|
147
|
+
* @see addCellFormatter
|
|
148
|
+
* @see addCellFormatters
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
153
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
154
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
155
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
156
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
157
|
+
* ]).getOrThrow()
|
|
158
|
+
* const columnHeader = ['Date-Time', 'Customer ID', 'Product ID', 'Purchase Price', 'Amount']
|
|
159
|
+
* const rowHeader = [1, 2, 3, 4]
|
|
160
|
+
*
|
|
161
|
+
* const tableData = TableData.fromDataFrame<string | number | Date>(data)
|
|
162
|
+
* .withColumnHeader(columnHeader)
|
|
163
|
+
* .flatMap(td => td.withRowHeader(rowHeader))
|
|
164
|
+
* .flatMap(tableData => TableFormatter.fromTableData(tableData)
|
|
165
|
+
* // add the default formatter for the column header, at the highest priority so that
|
|
166
|
+
* // it is the one that applies to the row representing the column header
|
|
167
|
+
* .addRowFormatters([0], defaultFormatter, Infinity)
|
|
168
|
+
* // add the default formatter for the row header, at the highest priority so that
|
|
169
|
+
* // it is the one that applies to the column representing the row header
|
|
170
|
+
* .flatMap(tf => tf.addColumnFormatters([0], defaultFormatter, Infinity))
|
|
171
|
+
* // add the column formatters for each column at the default (lowest) priority
|
|
172
|
+
* // (notice that the columns are shifted by one for the columns because the row-header
|
|
173
|
+
* // occupies the first column (index=0))
|
|
174
|
+
* .flatMap(tf => tf.addColumnFormatter(1, value => (value as Date).toLocaleDateString()))
|
|
175
|
+
* .flatMap(tf => tf.addColumnFormatter(2, value => defaultFormatter(value)))
|
|
176
|
+
* .flatMap(tf => tf.addColumnFormatter(4, value => `$ ${(value as number).toFixed(2)}`))
|
|
177
|
+
* .flatMap(tf => tf.addColumnFormatter(5, value => `${(value as number).toFixed(0)}`))
|
|
178
|
+
* // format the table data and get back a TableData<string>
|
|
179
|
+
* .flatMap(tf => tf.formatTable())
|
|
180
|
+
* )
|
|
181
|
+
* .getOrThrow()
|
|
182
|
+
*
|
|
183
|
+
* const expectedData = DataFrame.from<string>([
|
|
184
|
+
* ['2/1/2021', '12345', 'gnm-f234', '$ 123.45', '4'],
|
|
185
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
186
|
+
* ['2/3/2021', '34567', 'gnm-h234', '$ 3.65', '40'],
|
|
187
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '9'],
|
|
188
|
+
* ]).getOrThrow()
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
addColumnFormatters(columnIndexes: Array<number>, formatter: Formatter<V>, priority: number = 0): Result<TableFormatter<V>, string> {
|
|
192
|
+
return TableFormatter.addColumnFormatters(this, columnIndexes.slice(), formatter, priority)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static addColumnFormatters<V>(
|
|
196
|
+
tableFormatter: TableFormatter<V>,
|
|
197
|
+
columnIndexes: Array<number>,
|
|
198
|
+
formatter: Formatter<V>,
|
|
199
|
+
priority: number = 0
|
|
200
|
+
): Result<TableFormatter<V>, string> {
|
|
201
|
+
if (columnIndexes.length > 0) {
|
|
202
|
+
const columnIndex = columnIndexes.shift()
|
|
203
|
+
if (columnIndex != null) {
|
|
204
|
+
return tableFormatter
|
|
205
|
+
.addColumnFormatter(columnIndex, formatter, priority)
|
|
206
|
+
.flatMap(tf => TableFormatter.addColumnFormatters(tf, columnIndexes, formatter, priority))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return successResult(tableFormatter)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Formatters convert the row value types to formatted strings. The formatter used to format each cell in
|
|
214
|
+
* a given row depends on the priority of each formatter associated with that cell. The formatter with the
|
|
215
|
+
* highest priority is used. If two or more formatters for a given cell have the same priority, the selected
|
|
216
|
+
* formatter is indeterminant.
|
|
217
|
+
* @param rowIndex The index of the row to which to add the formatter
|
|
218
|
+
* @param formatter The formatter
|
|
219
|
+
* @param [priority = 0] The priority of this formatter. If cells have more than one associated formatter,
|
|
220
|
+
* the one with the highest priority number is used.
|
|
221
|
+
* @see addColumnFormatter
|
|
222
|
+
* @see addColumnFormatters
|
|
223
|
+
* @see addRowFormatters
|
|
224
|
+
* @example
|
|
225
|
+
* ```typescript
|
|
226
|
+
* // create the data
|
|
227
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
228
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
229
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
230
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
231
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
232
|
+
* ]).getOrThrow()
|
|
233
|
+
*
|
|
234
|
+
* // create the table-data object from the data, and then hand the table-data
|
|
235
|
+
* // to the table formatter, add column formats, and format the table, getting
|
|
236
|
+
* // back a new TableData<string>
|
|
237
|
+
* const tableData: TableData<string> = TableData.fromDataFrame<string | number | Date>(data)
|
|
238
|
+
* // from the table-data, create a table-formatter
|
|
239
|
+
* .flatMap(tableData => createTableFormatterFrom(tableData)
|
|
240
|
+
* // add a column formatter for the first column of dates
|
|
241
|
+
* .addColumnFormatter(0, value => (value as Date).toLocaleDateString())
|
|
242
|
+
* // add a column formatter to the second column of number
|
|
243
|
+
* .flatMap(tf => tf.addColumnFormatter(1, value => defaultFormatter(value)))
|
|
244
|
+
* // add a column formatter to the fourth column of currencies
|
|
245
|
+
* .flatMap(tf => tf.addColumnFormatter(3, value => `$ ${(value as number).toFixed(2)}`))
|
|
246
|
+
* .flatMap(tf => tf.addColumnFormatter(4, value => `${(value as number).toFixed(0)}`))
|
|
247
|
+
* // format the table into a new TableData<string> object
|
|
248
|
+
* .flatMap(tf => tf.formatTable())
|
|
249
|
+
* )
|
|
250
|
+
* .getOrThrow()
|
|
251
|
+
*
|
|
252
|
+
* // we expect the data-frame in the table data to be the following
|
|
253
|
+
* const expectedData = DataFrame.from<string>([
|
|
254
|
+
* ['2/1/2021', '12345', 'gnm-f234', '$ 123.45', '4'],
|
|
255
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
256
|
+
* ['2/3/2021', '34567', 'GNM-H234', '$ 3.65', '40'],
|
|
257
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '9'],
|
|
258
|
+
* ]).getOrThrow()
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
addRowFormatter(rowIndex: number, formatter: Formatter<V>, priority: number = 0): Result<TableFormatter<V>, string> {
|
|
262
|
+
return this.dataFrame
|
|
263
|
+
.tagRow<Formatting<V>>(rowIndex, TableFormatterType.ROW, {formatter, priority})
|
|
264
|
+
.map(data => new TableFormatter<V>(data))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Adds formatters to specified row indexes in a table formatter.
|
|
269
|
+
*
|
|
270
|
+
* @param rowIndexes An array of row indexes to which the formatter will be applied.
|
|
271
|
+
* @param formatter The formatting function to apply to the specified columns.
|
|
272
|
+
* @param [priority = 0] The priority level for the formatter. Higher priority formatters are applied first.
|
|
273
|
+
* @return A result indicating the success or failure of adding the row formatters.
|
|
274
|
+
* @see addColumnFormatter
|
|
275
|
+
* @see addRowFormatter
|
|
276
|
+
* @see addRowFormatters
|
|
277
|
+
* @see addCellFormatter
|
|
278
|
+
* @see addCellFormatters
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
282
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
283
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
284
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
285
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
286
|
+
* ]).getOrThrow()
|
|
287
|
+
* const columnHeader = ['Date-Time', 'Customer ID', 'Product ID', 'Purchase Price', 'Amount']
|
|
288
|
+
* const rowHeader = [1, 2, 3, 4]
|
|
289
|
+
*
|
|
290
|
+
* const tableData = TableData.fromDataFrame<string | number | Date>(data)
|
|
291
|
+
* .withColumnHeader(columnHeader)
|
|
292
|
+
* .flatMap(td => td.withRowHeader(rowHeader))
|
|
293
|
+
* .flatMap(tableData => TableFormatter.fromTableData(tableData)
|
|
294
|
+
* // add the default formatter for the column header, at the highest priority so that
|
|
295
|
+
* // it is the one that applies to the row representing the column header
|
|
296
|
+
* .addRowFormatters([0], defaultFormatter, Infinity)
|
|
297
|
+
* // add the default formatter for the row header, at the highest priority so that
|
|
298
|
+
* // it is the one that applies to the column representing the row header
|
|
299
|
+
* .flatMap(tf => tf.addColumnFormatters([0], defaultFormatter, Infinity))
|
|
300
|
+
* // add the column formatters for each column at the default (lowest) priority
|
|
301
|
+
* // (notice that the columns are shifted by one for the columns because the row-header
|
|
302
|
+
* // occupies the first column (index=0))
|
|
303
|
+
* .flatMap(tf => tf.addColumnFormatter(1, value => (value as Date).toLocaleDateString()))
|
|
304
|
+
* .flatMap(tf => tf.addColumnFormatter(2, value => defaultFormatter(value)))
|
|
305
|
+
* .flatMap(tf => tf.addColumnFormatter(4, value => `$ ${(value as number).toFixed(2)}`))
|
|
306
|
+
* .flatMap(tf => tf.addColumnFormatter(5, value => `${(value as number).toFixed(0)}`))
|
|
307
|
+
* // format the table data and get back a TableData<string>
|
|
308
|
+
* .flatMap(tf => tf.formatTable())
|
|
309
|
+
* )
|
|
310
|
+
* .getOrThrow()
|
|
311
|
+
*
|
|
312
|
+
* const expectedData = DataFrame.from<string>([
|
|
313
|
+
* ['2/1/2021', '12345', 'gnm-f234', '$ 123.45', '4'],
|
|
314
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
315
|
+
* ['2/3/2021', '34567', 'gnm-h234', '$ 3.65', '40'],
|
|
316
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '9'],
|
|
317
|
+
* ]).getOrThrow()
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
addRowFormatters(rowIndexes: Array<number>, formatter: Formatter<V>, priority: number = 0): Result<TableFormatter<V>, string> {
|
|
321
|
+
return TableFormatter.addRowFormatters(this, rowIndexes.slice(), formatter, priority)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private static addRowFormatters<V>(
|
|
325
|
+
tableFormatter: TableFormatter<V>,
|
|
326
|
+
rowIndexes: Array<number>,
|
|
327
|
+
formatter: Formatter<V>,
|
|
328
|
+
priority: number = 0
|
|
329
|
+
): Result<TableFormatter<V>, string> {
|
|
330
|
+
if (rowIndexes.length > 0) {
|
|
331
|
+
const rowIndex = rowIndexes.shift()
|
|
332
|
+
if (rowIndex != null) {
|
|
333
|
+
return tableFormatter
|
|
334
|
+
.addRowFormatter(rowIndex, formatter, priority)
|
|
335
|
+
.flatMap(tf => TableFormatter.addRowFormatters(tf, rowIndexes, formatter, priority))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return successResult(tableFormatter)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Adds a formatter to a specified cell based on the row and column indexes.
|
|
343
|
+
* @param rowIndex The row index of the cell.
|
|
344
|
+
* @param columnIndex The column index of the cell.
|
|
345
|
+
* @param formatter The formatter to apply to the cell.
|
|
346
|
+
* @param [priority = 0] The priority level for the formatter. Higher priority formatters are applied first.
|
|
347
|
+
* @return A result indicating the success or failure of adding the cell formatter.
|
|
348
|
+
* @see addColumnFormatter
|
|
349
|
+
* @see addRowFormatter
|
|
350
|
+
* @see addRowFormatters
|
|
351
|
+
* @see addCellFormatters
|
|
352
|
+
* @see addCellFormatters
|
|
353
|
+
* @example
|
|
354
|
+
* ```typescript
|
|
355
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
356
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
357
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
358
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
359
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
360
|
+
* ]).getOrThrow()
|
|
361
|
+
* const columnHeader = ['Date-Time', 'Customer ID', 'Product ID', 'Purchase Price', 'Amount']
|
|
362
|
+
* const rowHeader = [1, 2, 3, 4]
|
|
363
|
+
*
|
|
364
|
+
* const tableData = TableData.fromDataFrame<string | number | Date>(data)
|
|
365
|
+
* .withColumnHeader(columnHeader)
|
|
366
|
+
* .flatMap(td => td.withRowHeader(rowHeader))
|
|
367
|
+
* .flatMap(tableData => TableFormatter.fromTableData(tableData)
|
|
368
|
+
* // add the default formatter for the column header, at the highest priority so that
|
|
369
|
+
* // it is the one that applies to the row representing the column header
|
|
370
|
+
* .addRowFormatters([0], defaultFormatter, Infinity)
|
|
371
|
+
* // add the default formatter for the row header, at the highest priority so that
|
|
372
|
+
* // it is the one that applies to the column representing the row header
|
|
373
|
+
* .flatMap(tf => tf.addColumnFormatters([0], defaultFormatter, Infinity))
|
|
374
|
+
* // add the column formatters for each column at the default (lowest) priority
|
|
375
|
+
* // (notice that the columns are shifted by one for the columns because the row-header
|
|
376
|
+
* // occupies the first column (index=0))
|
|
377
|
+
* .flatMap(tf => tf.addColumnFormatter(1, value => (value as Date).toLocaleDateString()))
|
|
378
|
+
* .flatMap(tf => tf.addColumnFormatter(2, value => defaultFormatter(value)))
|
|
379
|
+
* .flatMap(tf => tf.addColumnFormatter(4, value => `$ ${(value as number).toFixed(2)}`))
|
|
380
|
+
* .flatMap(tf => tf.addColumnFormatter(5, value => `${(value as number).toFixed(0)}`))
|
|
381
|
+
* // override the cell formatter for a select set of cells
|
|
382
|
+
* .flatMap(tf => tf.addCellFormatter(1, 4, value => `${(value as number).toFixed(2)}`, 1000))
|
|
383
|
+
* .flatMap(tf => tf.addCellFormatters([[3, 5], [4, 5]], value => `${((value as number) * 10).toFixed(0)}`, 1000))
|
|
384
|
+
* // format the table data and get back a TableData<string>
|
|
385
|
+
* .flatMap(tf => tf.formatTable())
|
|
386
|
+
* )
|
|
387
|
+
* .getOrThrow()
|
|
388
|
+
*
|
|
389
|
+
* const expectedData = DataFrame.from<string>([
|
|
390
|
+
* ['2/1/2021', '12345', 'gnm-f234', '123.45', '4'], // overwrite (1, 4) to remove "$"
|
|
391
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
392
|
+
* ['2/3/2021', '34567', 'gnm-h234', '$ 3.65', '400'], // overwrite (3, 5) to multiply by 10
|
|
393
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '90'], // overwrite (4, 5) to multiply by 10
|
|
394
|
+
* ]).getOrThrow()
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
addCellFormatter(rowIndex: number, columnIndex: number, formatter: Formatter<V>, priority: number = 0): Result<TableFormatter<V>, string> {
|
|
398
|
+
return this.dataFrame
|
|
399
|
+
.tagCell(rowIndex, columnIndex, TableFormatterType.CELL, {formatter, priority})
|
|
400
|
+
.map(data => new TableFormatter<V>(data))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Adds formatters to specified cell indexes in a table formatter.
|
|
405
|
+
* @param cellIndexes An array of cell indexes to which the formatter will be applied.
|
|
406
|
+
* @param formatter The formatting function to apply to the specified cells.
|
|
407
|
+
* @param [priority = 0] The priority level for the formatter. Higher priority formatters are applied first.
|
|
408
|
+
* @return A result indicating the success or failure of adding the cell formatters.
|
|
409
|
+
* @see addColumnFormatter
|
|
410
|
+
* @see addRowFormatter
|
|
411
|
+
* @see addRowFormatters
|
|
412
|
+
* @see addCellFormatters
|
|
413
|
+
* @see addCellFormatters
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
417
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
418
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
419
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
420
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
421
|
+
* ]).getOrThrow()
|
|
422
|
+
* const columnHeader = ['Date-Time', 'Customer ID', 'Product ID', 'Purchase Price', 'Amount']
|
|
423
|
+
* const rowHeader = [1, 2, 3, 4]
|
|
424
|
+
*
|
|
425
|
+
* const tableData = TableData.fromDataFrame<string | number | Date>(data)
|
|
426
|
+
* .withColumnHeader(columnHeader)
|
|
427
|
+
* .flatMap(td => td.withRowHeader(rowHeader))
|
|
428
|
+
* .flatMap(tableData => TableFormatter.fromTableData(tableData)
|
|
429
|
+
* // add the default formatter for the column header, at the highest priority so that
|
|
430
|
+
* // it is the one that applies to the row representing the column header
|
|
431
|
+
* .addRowFormatters([0], defaultFormatter, Infinity)
|
|
432
|
+
* // add the default formatter for the row header, at the highest priority so that
|
|
433
|
+
* // it is the one that applies to the column representing the row header
|
|
434
|
+
* .flatMap(tf => tf.addColumnFormatters([0], defaultFormatter, Infinity))
|
|
435
|
+
* // add the column formatters for each column at the default (lowest) priority
|
|
436
|
+
* // (notice that the columns are shifted by one for the columns because the row-header
|
|
437
|
+
* // occupies the first column (index=0))
|
|
438
|
+
* .flatMap(tf => tf.addColumnFormatter(1, value => (value as Date).toLocaleDateString()))
|
|
439
|
+
* .flatMap(tf => tf.addColumnFormatter(2, value => defaultFormatter(value)))
|
|
440
|
+
* .flatMap(tf => tf.addColumnFormatter(4, value => `$ ${(value as number).toFixed(2)}`))
|
|
441
|
+
* .flatMap(tf => tf.addColumnFormatter(5, value => `${(value as number).toFixed(0)}`))
|
|
442
|
+
* // override the cell formatter for a select set of cells
|
|
443
|
+
* .flatMap(tf => tf.addCellFormatter(1, 4, value => `${(value as number).toFixed(2)}`, 1000))
|
|
444
|
+
* .flatMap(tf => tf.addCellFormatters([[3, 5], [4, 5]], value => `${((value as number) * 10).toFixed(0)}`, 1000))
|
|
445
|
+
* // format the table data and get back a TableData<string>
|
|
446
|
+
* .flatMap(tf => tf.formatTable())
|
|
447
|
+
* )
|
|
448
|
+
* .getOrThrow()
|
|
449
|
+
*
|
|
450
|
+
* const expectedData = DataFrame.from<string>([
|
|
451
|
+
* ['2/1/2021', '12345', 'gnm-f234', '123.45', '4'], // overwrite (1, 4) to remove "$"
|
|
452
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
453
|
+
* ['2/3/2021', '34567', 'gnm-h234', '$ 3.65', '400'], // overwrite (3, 5) to multiply by 10
|
|
454
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '90'], // overwrite (4, 5) to multiply by 10
|
|
455
|
+
* ]).getOrThrow()
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
addCellFormatters(cellIndexes: Array<[x: number, y: number]>, formatter: Formatter<V>, priority: number = 0): Result<TableFormatter<V>, string> {
|
|
459
|
+
return TableFormatter.addCellFormatters(this, cellIndexes.slice(), formatter, priority)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private static addCellFormatters<V>(
|
|
463
|
+
tableFormatter: TableFormatter<V>,
|
|
464
|
+
cellIndexes: Array<[x: number, y: number]>,
|
|
465
|
+
formatter: Formatter<V>,
|
|
466
|
+
priority: number = 0
|
|
467
|
+
): Result<TableFormatter<V>, string> {
|
|
468
|
+
if (cellIndexes.length > 0) {
|
|
469
|
+
const [rowIndex, columnIndex] = cellIndexes.shift() ?? [undefined, undefined]
|
|
470
|
+
if (rowIndex != null && columnIndex != null) {
|
|
471
|
+
return tableFormatter
|
|
472
|
+
.addCellFormatter(rowIndex, columnIndex, formatter, priority)
|
|
473
|
+
.flatMap(tf => TableFormatter.addCellFormatters(tf, cellIndexes, formatter, priority))
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return successResult(tableFormatter)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Formats the table headers, footer, and values using the formatters that have been added
|
|
481
|
+
* to this `TableData<V>` object and returns a new `TableData<string>` object where all the
|
|
482
|
+
* elements have been converted to a formatted string.
|
|
483
|
+
* @return a new `TableData<string>` object where all the elements have been converted to a
|
|
484
|
+
* formatted string.
|
|
485
|
+
* @example
|
|
486
|
+
* ```typescript
|
|
487
|
+
* function dateTimeFor(day: number, hour: number): Date {
|
|
488
|
+
* return new Date(2021, 1, day, hour, 0, 0, 0);
|
|
489
|
+
* }
|
|
490
|
+
*
|
|
491
|
+
* // the headers for the table
|
|
492
|
+
* const columnHeader = ['Date-Time', 'Customer ID', 'Product ID', 'Purchase Price', 'Amount']
|
|
493
|
+
* const rowHeader = [1, 2, 3, 4]
|
|
494
|
+
*
|
|
495
|
+
* // this is the actual data used to creat the data table (in row-form)
|
|
496
|
+
* const data = DataFrame.from<string | number | Date>([
|
|
497
|
+
* [dateTimeFor(1, 1), 12345, 'gnm-f234', 123.45, 4],
|
|
498
|
+
* [dateTimeFor(2, 2), 23456, 'gnm-g234', 23.45, 5],
|
|
499
|
+
* [dateTimeFor(3, 3), 34567, 'gnm-h234', 3.65, 40],
|
|
500
|
+
* [dateTimeFor(4, 4), 45678, 'gnm-i234', 314.15, 9],
|
|
501
|
+
* ]).getOrThrow()
|
|
502
|
+
*
|
|
503
|
+
* // this is what we expect that formatted data to look like
|
|
504
|
+
* const expectedData = DataFrame.from<string>([
|
|
505
|
+
* ['2/1/2021', '12345', 'gnm-f234', '$ 123.45', '4'],
|
|
506
|
+
* ['2/2/2021', '23456', 'gnm-g234', '$ 23.45', '5'],
|
|
507
|
+
* ['2/3/2021', '34567', 'gnm-h234', '$ 3.65', '40'],
|
|
508
|
+
* ['2/4/2021', '45678', 'gnm-i234', '$ 314.15', '9'],
|
|
509
|
+
* ]).getOrThrow()
|
|
510
|
+
*
|
|
511
|
+
* // 1. create a data-table that has a column-header and a row-header and mixed-type
|
|
512
|
+
* // data (number, string, Date)
|
|
513
|
+
* // 2. add formatters for the row and column headers (at highest priority)
|
|
514
|
+
* // 3. add formatters for some of the other data columns
|
|
515
|
+
* // 4. format the table
|
|
516
|
+
* const tableData = TableData.fromDataFrame<string | number | Date>(data)
|
|
517
|
+
* .withColumnHeader(columnHeader)
|
|
518
|
+
* .flatMap(table => table.withRowHeader(rowHeader))
|
|
519
|
+
* // add the default formatter for the column header, at the highest priority so that
|
|
520
|
+
* // it is the one that applies to the row representing the column header
|
|
521
|
+
* .flatMap(td => td.addRowFormatter(0, defaultFormatter, Infinity))
|
|
522
|
+
* // add the default formatter for the row header, at the highest priority so that
|
|
523
|
+
* // it is the one that applies to the column representing the row header
|
|
524
|
+
* .flatMap(td => td.addColumnFormatter(0, defaultFormatter, Infinity))
|
|
525
|
+
* // add the column formatters for each column at the default (lowest) priority
|
|
526
|
+
* // (notice that the columns are shifted by one for the columns because the row-header
|
|
527
|
+
* // occupies the first column (index=0))
|
|
528
|
+
* .flatMap(td => td.addColumnFormatter(1, value => (value as Date).toLocaleDateString()))
|
|
529
|
+
* .flatMap(td => td.addColumnFormatter(2, value => defaultFormatter(value)))
|
|
530
|
+
* .flatMap(td => td.addColumnFormatter(4, value => `$ ${(value as number).toFixed(2)}`))
|
|
531
|
+
* .flatMap(td => td.addColumnFormatter(5, value => `${(value as number).toFixed(0)}`))
|
|
532
|
+
* // format the table data and get back a TableData<string>
|
|
533
|
+
* .map(td => td.formatTable())
|
|
534
|
+
* .getOrThrow()
|
|
535
|
+
*
|
|
536
|
+
* // the column header of the formatted table should be the same as the one specified
|
|
537
|
+
* expect(tableData.columnHeader().getOrThrow()).toEqual(columnHeader)
|
|
538
|
+
*
|
|
539
|
+
* // the row header of the formatted table should be the same as the one specified
|
|
540
|
+
* expect(tableData.rowHeader().getOrThrow()).toEqual(rowHeader.map(hdr => defaultFormatter(hdr)))
|
|
541
|
+
*
|
|
542
|
+
* // the data should be equal to the expected data
|
|
543
|
+
* expect(tableData.data().map(df => df.equals(expectedData)).getOrThrow()).toBeTruthy()
|
|
544
|
+
* ```
|
|
545
|
+
*/
|
|
546
|
+
formatTable<C extends TagCoordinate>(): Result<TableData<string>, string> {
|
|
547
|
+
return this.formatTableInto<C, TableData<string>>(dataFrame => TableData.fromDataFrame<string>(dataFrame))
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Generally, the {@link TableFormatter} formats the {@link DataFrame} into a `TableData<string>`
|
|
552
|
+
* where all the elements of the {@link TableData} are a string. Because a formatted table does
|
|
553
|
+
* not necessarily have to be a `TableData<string>` (which would contain a `DataFrame<string>`,
|
|
554
|
+
* this method allows mapping a {@link DataFrame} into any desired type.
|
|
555
|
+
* @param mapper A function that takes a `DataFrame<string>` and returns a desired type (`D`).
|
|
556
|
+
* @return A `Result<D, string>` where the `Result<D, string>` is either success, containing the
|
|
557
|
+
* desired type (`D`), or failure, containing an error message.
|
|
558
|
+
* @see formatTable
|
|
559
|
+
*/
|
|
560
|
+
formatTableInto<C extends TagCoordinate, D = TableData<string>>(mapper: (dataFrame: DataFrame<string>) => D): Result<D, string> {
|
|
561
|
+
const formattingFailures: Array<string> = []
|
|
562
|
+
const formattedDataFrame = this
|
|
563
|
+
.dataFrame
|
|
564
|
+
.mapElements<string>((elem, row, col) => {
|
|
565
|
+
const tags = this.dataFrame
|
|
566
|
+
.tagsFor(row, col)
|
|
567
|
+
.filter(tag => isFormattingTag(tag)) as Array<Tag<Formatting<V>, C>>
|
|
568
|
+
const sorted = tags
|
|
569
|
+
.sort((t1: Tag<Formatting<V>, C>, t2: Tag<Formatting<V>, C>) => t2.value.priority - t1.value.priority)
|
|
570
|
+
if (sorted.length > 0) {
|
|
571
|
+
const formatter = sorted[0].value.formatter
|
|
572
|
+
try {
|
|
573
|
+
return formatter(elem)
|
|
574
|
+
} catch (e) {
|
|
575
|
+
formattingFailures.push(
|
|
576
|
+
`(TableData::formatTable) Failed to format cell (${row}, ${col}); value: ${elem}; error: ${e}`
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return defaultFormatter<V>(elem)
|
|
581
|
+
})
|
|
582
|
+
if (formattingFailures.length === 0) {
|
|
583
|
+
return successResult(mapper(formattedDataFrame))
|
|
584
|
+
}
|
|
585
|
+
return failureResult(formattingFailures.concat('').join('\n'))
|
|
586
|
+
}
|
|
587
|
+
}
|