svg-table 0.0.1

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/d3types.ts +17 -0
  4. package/dist/d3types.d.ts +12 -0
  5. package/dist/d3types.d.ts.map +1 -0
  6. package/dist/d3types.js +3 -0
  7. package/dist/d3types.js.map +1 -0
  8. package/dist/index.d.ts +10 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +36 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/stylings.d.ts +206 -0
  13. package/dist/stylings.d.ts.map +1 -0
  14. package/dist/stylings.js +123 -0
  15. package/dist/stylings.js.map +1 -0
  16. package/dist/tableData.d.ts +168 -0
  17. package/dist/tableData.d.ts.map +1 -0
  18. package/dist/tableData.js +329 -0
  19. package/dist/tableData.js.map +1 -0
  20. package/dist/tableData.test.d.ts +2 -0
  21. package/dist/tableData.test.d.ts.map +1 -0
  22. package/dist/tableData.test.js +259 -0
  23. package/dist/tableData.test.js.map +1 -0
  24. package/dist/tableFormatter.d.ts +179 -0
  25. package/dist/tableFormatter.d.ts.map +1 -0
  26. package/dist/tableFormatter.js +298 -0
  27. package/dist/tableFormatter.js.map +1 -0
  28. package/dist/tableFormatter.test.d.ts +2 -0
  29. package/dist/tableFormatter.test.d.ts.map +1 -0
  30. package/dist/tableFormatter.test.js +101 -0
  31. package/dist/tableFormatter.test.js.map +1 -0
  32. package/dist/tableStyler.d.ts +310 -0
  33. package/dist/tableStyler.d.ts.map +1 -0
  34. package/dist/tableStyler.js +665 -0
  35. package/dist/tableStyler.js.map +1 -0
  36. package/dist/tableStyler.test.d.ts +2 -0
  37. package/dist/tableStyler.test.d.ts.map +1 -0
  38. package/dist/tableStyler.test.js +225 -0
  39. package/dist/tableStyler.test.js.map +1 -0
  40. package/dist/tableSvg.d.ts +41 -0
  41. package/dist/tableSvg.d.ts.map +1 -0
  42. package/dist/tableSvg.js +634 -0
  43. package/dist/tableSvg.js.map +1 -0
  44. package/dist/tableUtils.d.ts +14 -0
  45. package/dist/tableUtils.d.ts.map +1 -0
  46. package/dist/tableUtils.js +18 -0
  47. package/dist/tableUtils.js.map +1 -0
  48. package/eslint.config.js +23 -0
  49. package/index.ts +82 -0
  50. package/jest.config.js +5 -0
  51. package/package.json +44 -0
  52. package/stylings.ts +311 -0
  53. package/svg-table-0.0.1-snapshot.tgz +0 -0
  54. package/tableData.test.ts +290 -0
  55. package/tableData.ts +359 -0
  56. package/tableFormatter.test.ts +122 -0
  57. package/tableFormatter.ts +306 -0
  58. package/tableStyler.test.ts +268 -0
  59. package/tableStyler.ts +798 -0
  60. package/tableSvg.ts +820 -0
  61. package/tableUtils.ts +20 -0
  62. package/tsconfig.json +102 -0
package/tableStyler.ts ADDED
@@ -0,0 +1,798 @@
1
+ import {TableData} from "./tableData";
2
+ import {CellCoordinate, ColumnCoordinate, DataFrame, RowCoordinate, type Tag, type TagValue} from "data-frame-ts";
3
+ import {failureResult, type Result, successResult} from "result-fn";
4
+ import {
5
+ type Background,
6
+ type Border,
7
+ type CellStyle,
8
+ type ColumnHeaderStyle,
9
+ type ColumnStyle,
10
+ defaultCellStyle,
11
+ defaultColumnHeaderStyle,
12
+ defaultColumnStyle,
13
+ defaultDimension,
14
+ defaultFooterStyle,
15
+ defaultRowHeaderStyle,
16
+ defaultRowStyle,
17
+ defaultTableBackground,
18
+ defaultTableFont,
19
+ defaultTableMargin,
20
+ defaultTablePadding,
21
+ type Dimension,
22
+ type FooterStyle,
23
+ type Padding,
24
+ type RowHeaderStyle,
25
+ type RowStyle,
26
+ type Styling,
27
+ stylingFor,
28
+ type Stylings,
29
+ type TableFont,
30
+ type TableStylerProps,
31
+ TableStyleType, defaultBorder, Margin
32
+ } from "./stylings";
33
+
34
+ /**
35
+ * Represents a table with applied styles.
36
+ * Provides methods to access the styling information for different parts of the table.
37
+ */
38
+ export class StyledTable<V> {
39
+
40
+ /**
41
+ * Creates a new StyledTable instance.
42
+ * @param dataFrame The data frame containing the table data
43
+ * @param font The font settings for the table
44
+ * @param border The border settings for the table
45
+ * @param background The background settings for the table
46
+ * @param dimension The dimension settings for the table
47
+ * @param padding The padding settings for the table
48
+ * @param margin The margin settings for the table
49
+ */
50
+ constructor(
51
+ private readonly dataFrame: DataFrame<V>,
52
+ private readonly font: TableFont,
53
+ private readonly border: Border,
54
+ private readonly background: Background,
55
+ private readonly dimension: Pick<Dimension, "width" | "height">,
56
+ private readonly padding: Padding,
57
+ private readonly margin: Margin,
58
+ ) {
59
+ }
60
+
61
+ /**
62
+ * @return A copy of the {@link DataFrame} with all the styling and formatting tags
63
+ */
64
+ data(): DataFrame<V> {
65
+ return this.dataFrame.copy()
66
+ }
67
+
68
+ tableData(): TableData<V> {
69
+ // fromDataFrame makes a copy of the data frame
70
+ return TableData.fromDataFrame(this.dataFrame)
71
+ }
72
+
73
+ /**
74
+ * Returns the font settings for the table.
75
+ * @returns A copy of the table's font settings
76
+ */
77
+ tableFont(): TableFont {
78
+ return {...this.font}
79
+ }
80
+
81
+ /**
82
+ * Returns the border settings for the table.
83
+ * @returns A copy of the table's border settings
84
+ */
85
+ tableBorder(): Border {
86
+ return {...this.border}
87
+ }
88
+
89
+ /**
90
+ * Returns the background settings for the table.
91
+ * @returns A copy of the table's background settings
92
+ */
93
+ tableBackground(): Background {
94
+ return {...this.background}
95
+ }
96
+
97
+ /**
98
+ * Returns the dimension settings for the table.
99
+ * @returns A copy of the table's dimension settings
100
+ */
101
+ tableDimensions(): Pick<Dimension, "width" | "height"> {
102
+ return {...this.dimension}
103
+ }
104
+
105
+ /**
106
+ * Returns the padding settings for the table.
107
+ * @returns A copy of the table's padding settings
108
+ */
109
+ tablePadding(): Padding {
110
+ return {...this.padding}
111
+ }
112
+
113
+ /**
114
+ * Returns the margin settings for the table.
115
+ * @returns A copy of the table's margin settings
116
+ */
117
+ tableMargin(): Margin {
118
+ return {...this.margin}
119
+ }
120
+
121
+ /**
122
+ * Retrieves styling tags for a specific column.
123
+ * @param columnIndex The index of the column
124
+ * @param tagStyleType The type of style tag to retrieve
125
+ * @returns A Result containing the tag if found, or an error message
126
+ * @private
127
+ */
128
+ private columnTagsFor<T extends TagValue>(columnIndex: number, tagStyleType: TableStyleType): Result<Tag<Styling<T>, ColumnCoordinate>, string> {
129
+ // find all the tags and type them to row-header tags
130
+ const tags = this.dataFrame
131
+ .columnTagsFor(columnIndex)
132
+ .filter(tag => tag.matchesId(tagStyleType, ColumnCoordinate.of(columnIndex)))
133
+ .map(tag => tag as Tag<Styling<T>, ColumnCoordinate>)
134
+
135
+ if (tags.length === 0) {
136
+ return failureResult(`(StyledTable::columnTagsFor) No matching column-style tags found for table; column_index: ${columnIndex}; tag_type: ${tagStyleType}`)
137
+ }
138
+ // when there are more than one tag representing the row-header style, then sort based on priority and
139
+ // take the first one
140
+ if (tags.length > 1) {
141
+ tags.sort((tagA, tagB) => tagB.value.priority - tagA.value.priority)
142
+ }
143
+ return successResult(tags[0])
144
+ }
145
+
146
+ /**
147
+ * Retrieves styling tags for a specific row.
148
+ * @param rowIndex The index of the row
149
+ * @param tagStyleType The type of style tag to retrieve (can be more than one)
150
+ * @returns A Result containing the tag if found, or an error message
151
+ * @private
152
+ */
153
+ private rowTagsFor<S extends TagValue>(rowIndex: number, ...tagStyleType: Array<TableStyleType>): Result<Tag<Styling<S>, RowCoordinate>, string> {
154
+ // find all the tags and type them to column-header tags
155
+ const tags = this.dataFrame
156
+ .rowTagsFor(rowIndex)
157
+ .filter(tag => tagStyleType.filter(styleType => tag.matchesId(styleType, RowCoordinate.of(rowIndex))).length > 0)
158
+ .map(tag => tag as Tag<Styling<S>, RowCoordinate>)
159
+
160
+ if (tags.length === 0) {
161
+ return failureResult(`(StyledTable::rowTagsFor) No matching row-style tags found for table; row_index: ${rowIndex}; tag_type: ${tagStyleType}`)
162
+ }
163
+ // when there are more than one tag representing the column-header, the sort based on the priority and
164
+ // take the first one
165
+ if (tags.length > 1) {
166
+ tags.sort((tagA, tagB) => tagB.value.priority - tagA.value.priority)
167
+ }
168
+ return successResult(tags[0])
169
+ }
170
+
171
+ hasRowHeader(): boolean {
172
+ return TableData.hasRowHeader(this.dataFrame)
173
+ }
174
+
175
+ hasColumnHeader(): boolean {
176
+ return TableData.hasColumnHeader(this.dataFrame)
177
+ }
178
+
179
+ hasFooter(): boolean {
180
+ return TableData.hasFooter(this.dataFrame)
181
+ }
182
+
183
+ /**
184
+ * Gets the style for the row header.
185
+ * @returns A Result containing the row header style if found, or an error message
186
+ */
187
+ rowHeaderStyle(): Result<Styling<RowHeaderStyle>, string> {
188
+ if (!TableData.hasRowHeader(this.dataFrame)) {
189
+ return failureResult("(StyledTable::rowHeaderStyle) The table data does not have a row header")
190
+ }
191
+ return this
192
+ .columnTagsFor<RowHeaderStyle>(0, TableStyleType.ROW_HEADER)
193
+ .map(tag => tag.value as Styling<RowHeaderStyle>)
194
+ }
195
+
196
+ /**
197
+ * Gets the style for the column header.
198
+ * @returns A Result containing the column header style if found, or an error message
199
+ */
200
+ columnHeaderStyle(): Result<Styling<ColumnHeaderStyle>, string> {
201
+ if (!TableData.hasColumnHeader(this.dataFrame)) {
202
+ return failureResult("(StyledTable::columnHeaderStyle) The table data does not have a column header")
203
+ }
204
+ return this
205
+ .rowTagsFor<ColumnHeaderStyle>(0, TableStyleType.COLUMN_HEADER)
206
+ .map(tag => tag.value as Styling<ColumnHeaderStyle>)
207
+ }
208
+
209
+ footerStyle(): Result<Styling<FooterStyle>, string> {
210
+ if (!TableData.hasFooter(this.dataFrame)) {
211
+ return failureResult("(StyledTable::footerStyle) The table data does not have a footer")
212
+ }
213
+ return this
214
+ .rowTagsFor<FooterStyle>(this.dataFrame.rowCount() - 1, TableStyleType.FOOTER)
215
+ .map(tag => tag.value as Styling<FooterStyle>)
216
+ }
217
+
218
+ /**
219
+ * Gets the style for a specific row.
220
+ * @param rowIndex The index of the row
221
+ * @returns A Result containing the row style if found, or an error message
222
+ */
223
+ rowStyleFor(rowIndex: number): Result<Styling<RowStyle>, string> {
224
+ return this
225
+ .rowTagsFor<RowStyle>(rowIndex, TableStyleType.ROW)
226
+ .map(tag => tag.value as Styling<RowStyle>)
227
+ }
228
+
229
+ /**
230
+ * Gets the style for a specific column.
231
+ * @param columnIndex The index of the column
232
+ * @returns A Result containing the column style if found, or an error message
233
+ */
234
+ columnStyleFor(columnIndex: number): Result<Styling<ColumnStyle>, string> {
235
+ return this
236
+ .columnTagsFor<ColumnStyle>(columnIndex, TableStyleType.COLUMN)
237
+ .map(tag => tag.value as Styling<ColumnStyle>)
238
+ }
239
+
240
+ /**
241
+ * Gets the style for a specific cell.
242
+ * @param rowIndex The row index of the cell
243
+ * @param columnIndex The column index of the cell
244
+ * @returns A Result containing the cell style if found, or an error message
245
+ */
246
+ cellStyleFor(rowIndex: number, columnIndex: number): Result<Styling<CellStyle>, string> {
247
+ // find all the tags and type them to column-header tags
248
+ const tags = this.dataFrame
249
+ .cellTagsFor(rowIndex, columnIndex)
250
+ .filter(tag => tag.matchesId(TableStyleType.CELL, CellCoordinate.of(rowIndex, columnIndex)))
251
+ .map(tag => tag as Tag<Styling<CellStyle>, CellCoordinate>)
252
+
253
+ if (tags.length === 0) {
254
+ return failureResult(`(StyledTable::cellStyleFor) No matching cell-style tags found for table; row_index: ${rowIndex}; column_index: ${columnIndex}`)
255
+ }
256
+ // when there are more than one tag representing the column-header, the sort based on the priority and
257
+ // take the first one
258
+ if (tags.length > 1) {
259
+ tags.sort((tagA, tagB) => tagB.value.priority - tagA.value.priority)
260
+ }
261
+ return successResult(tags[0].value as Styling<CellStyle>)
262
+ }
263
+
264
+ /**
265
+ * Unlike the methods that retrieve the particular styles, say for a cell, a column header,
266
+ * and so forth, this method returns a {@link CellStyle} calculated from all the styles
267
+ * that apply to the specified cell by using the style properties with the highest priority.
268
+ * <p>
269
+ * Retrieves the styles to be applied to a specific data cell in the table. This method
270
+ * accounts for column headers, row headers, and footers. For example, regardless of whether the
271
+ * table has a column header, a row index of 0 refers to the first row of data. And
272
+ * regardless of whether the table has a row header, a column index of 0 refers to the
273
+ * first column of data.
274
+ *
275
+ * @param rowIndex - The index of the row in the table data for which styles are required.
276
+ * Must be within the valid row index range.
277
+ * @param columnIndex - The index of the column in the table data for which styles are required.
278
+ * Must be within the valid column index range.
279
+ * @return A result object containing the cell style if the indices are valid, or an error
280
+ * message if they are not.
281
+ */
282
+ stylesForDataCoordinates(rowIndex: number, columnIndex: number): Result<CellStyle, string> {
283
+ if (
284
+ rowIndex < 0 || rowIndex >= TableData.dataRowCount(this.dataFrame) ||
285
+ columnIndex < 0 || columnIndex >= TableData.dataColumnCount(this.dataFrame)
286
+ ) {
287
+ return failureResult(
288
+ `(StyledTable::dataCellStyles) Invalid row and/or column index for data; row_index${rowIndex}` +
289
+ `; column_index: ${columnIndex}` +
290
+ `; valid_row_index: [0, ${TableData.dataRowCount(this.dataFrame)})` +
291
+ `; valid_column_index: [0, ${TableData.dataColumnCount(this.dataFrame)})` +
292
+ `; has_column_header: ${TableData.hasColumnHeader(this.dataFrame)}` +
293
+ `; has_row_header: ${TableData.hasRowHeader(this.dataFrame)}` +
294
+ `; has_footer: ${TableData.hasFooter(this.dataFrame)}`
295
+ )
296
+ }
297
+
298
+ const columnHeaderOffset: number = TableData.hasColumnHeader(this.dataFrame) ? 1 : 0
299
+ const rowHeaderOffset: number = TableData.hasRowHeader(this.dataFrame) ? 1 : 0
300
+ return this.stylesForTableCoordinates(rowIndex + columnHeaderOffset, columnIndex + rowHeaderOffset)
301
+ }
302
+
303
+ /**
304
+ * Unlike the methods that retrieve the particular styles, say for a cell, a column header,
305
+ * and so forth, this method returns a {@link CellStyle} calculated from all the styles
306
+ * that apply to the specified cell by using the style properties with the highest priority.
307
+ * <p>
308
+ * Calculates the style for the cell based on the styles applied to the table and their
309
+ * relative priority. The row and column indexes refer to the entire table and do not account
310
+ * for column headers, row headers, or footers.
311
+ * @param rowIndex The index of the row in the entire table. For example, if the table has column
312
+ * headers, then a rowIndex of 0 would be that column header.
313
+ * @param columnIndex The index of the column in the entire table. For example, if the table has
314
+ * row headers, then a column index of 0 would be the row header
315
+ * @return A {@link Result} holding the {@link CellStyle}; or a failure {@link Result} if the
316
+ * row or column indexes are out of range.
317
+ * @see stylesForDataCoordinates
318
+ */
319
+ stylesForTableCoordinates(rowIndex: number, columnIndex: number): Result<CellStyle, string> {
320
+ if (rowIndex < 0 || rowIndex >= this.dataFrame.rowCount() || columnIndex < 0 || columnIndex >= this.dataFrame.columnCount()) {
321
+ return failureResult(
322
+ `(StyledTable::stylesFor) Invalid row and/or column index` +
323
+ `; row_index: ${rowIndex}` +
324
+ `; column_index: ${columnIndex}` +
325
+ `; valid_row_index: [0, ${this.dataFrame.rowCount()})` +
326
+ `; valid_column_index: [0, ${this.dataFrame.columnCount()})`
327
+ )
328
+ }
329
+
330
+ //
331
+ // determine what styles may be available
332
+ const availableStyling: Array<Stylings> = []
333
+ if (rowIndex === 0) {
334
+ this.columnHeaderStyle().onSuccess(styling => availableStyling.push(styling))
335
+ }
336
+ if (columnIndex === 0) {
337
+ this.rowHeaderStyle().onSuccess(styling => availableStyling.push(styling))
338
+ }
339
+ if (rowIndex === this.dataFrame.rowCount() - 1) {
340
+ this.footerStyle().onSuccess(styling => availableStyling.push(styling))
341
+ }
342
+ this.rowStyleFor(rowIndex).onSuccess(styling => availableStyling.push(styling))
343
+ this.columnStyleFor(columnIndex).onSuccess(styling => availableStyling.push(styling))
344
+
345
+ // the cell style is handled differently when it doesn't exist because we need a default set of
346
+ // values for the cell style in case not all properties are found in the available styles
347
+ this.cellStyleFor(rowIndex, columnIndex)
348
+ .onSuccess(styling => availableStyling.push(styling))
349
+ .onFailure(() => availableStyling.push({style: defaultCellStyle, priority: -1}))
350
+
351
+ //
352
+ // calculate the style for the cell based on the priorities and available styles
353
+ const cellStyle = availableStyling
354
+ .sort((stylingA: Stylings, stylingB: Stylings) => stylingA.priority - stylingB.priority)
355
+ .reduce((style: CellStyle, curr: Stylings) => ({
356
+ font:
357
+ (curr.style.hasOwnProperty('font') ?
358
+ // @ts-ignore
359
+ {...style.font, ...curr.style.font} as TableFont : (
360
+ style.hasOwnProperty('font') ? {...style.font} : defaultTableFont)
361
+ ),
362
+ alignText:
363
+ (curr.style.hasOwnProperty('alignText') ?
364
+ // @ts-ignore
365
+ curr.style.alignText as TextAlignment : (
366
+ style.hasOwnProperty('alignText') ? style.alignText : defaultColumnStyle.alignText)
367
+ ),
368
+ verticalAlignText:
369
+ (curr.style.hasOwnProperty('verticalAlignText') ?
370
+ // @ts-ignore
371
+ curr.style.verticalAlignText as VerticalTextAlignment : (
372
+ style.hasOwnProperty('verticalAlignText') ? style.verticalAlignText : defaultColumnStyle.verticalAlignText
373
+ )
374
+ ),
375
+ background:
376
+ (curr.style.hasOwnProperty('background') ?
377
+ // @ts-ignore
378
+ {...style.background, ...curr.style.background} as Background : (
379
+ style.hasOwnProperty('background') ? style.background : defaultTableBackground)
380
+ ),
381
+ dimension:
382
+ (curr.style.hasOwnProperty('dimension') ?
383
+ // @ts-ignore
384
+ {...style.dimension, ...curr.style.dimension} as Dimension : (
385
+ style.hasOwnProperty('dimension') ? {...style.dimension} : defaultDimension)
386
+ ),
387
+ padding:
388
+ (curr.style.hasOwnProperty('padding') ?
389
+ // @ts-ignore
390
+ {...style.padding, ...curr.style.padding} as Padding : (
391
+ style.hasOwnProperty('padding') ? {...style.padding} : defaultTablePadding)
392
+ ),
393
+ border:
394
+ (curr.style.hasOwnProperty('border') ?
395
+ // @ts-ignore
396
+ {...style.border, ...curr.style.border} as Border : (
397
+ style.hasOwnProperty('border') ? {...style.border} : defaultBorder)
398
+ ),
399
+ }), defaultCellStyle)
400
+
401
+ return successResult(cellStyle)
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Builder class for creating styled tables.
407
+ * Provides methods to configure various styling aspects of a table.
408
+ */
409
+ export class TableStyler<V> {
410
+
411
+ /**
412
+ * Private constructor to enforce factory method usage.
413
+ * @param dataFrame The data frame containing the table data
414
+ * @param font The font settings for the table
415
+ * @param border The border settings for the table
416
+ * @param background The background settings for the table
417
+ * @param dimension The dimension settings for the table
418
+ * @param padding The padding settings for the table
419
+ * @param margin The margin settings for the table
420
+ * @param errors Array to collect error messages during styling operations
421
+ */
422
+ private constructor(
423
+ private dataFrame: DataFrame<V>,
424
+ private font: TableFont = defaultTableFont,
425
+ private border: Border = defaultBorder,
426
+ private background: Background = defaultTableBackground,
427
+ private dimension: Pick<Dimension, "width" | "height"> = {width: NaN, height: NaN},
428
+ private padding: Padding = defaultTablePadding,
429
+ private margin: Margin = defaultTableMargin,
430
+ private readonly errors: Array<string> = []
431
+ ) {
432
+ }
433
+
434
+ /**
435
+ * Creates a TableStyler from a TableData object.
436
+ * @param tableData The TableData object to style
437
+ * @returns A new TableStyler instance
438
+ */
439
+ static fromTableData<V>(tableData: TableData<V>): TableStyler<V> {
440
+ return new TableStyler<V>(tableData.unwrapDataFrame())
441
+ }
442
+
443
+ /**
444
+ * Creates a TableStyler from a DataFrame object.
445
+ * @param dataFrame The DataFrame object to style
446
+ * @returns A new TableStyler instance
447
+ */
448
+ static fromDataFrame<V>(dataFrame: DataFrame<V>): TableStyler<V> {
449
+ return new TableStyler<V>(dataFrame.copy())
450
+ }
451
+
452
+ /**
453
+ * Creates a copy of this TableStyler instance.
454
+ * @returns A new TableStyler instance with the same properties
455
+ */
456
+ copy(): TableStyler<V> {
457
+ return new TableStyler<V>(
458
+ this.dataFrame,
459
+ this.font,
460
+ this.border,
461
+ this.background,
462
+ this.dimension,
463
+ this.padding,
464
+ this.margin,
465
+ this.errors
466
+ )
467
+ }
468
+
469
+ /**
470
+ * Creates a new TableStyler with updated properties.
471
+ * @param properties Partial properties to update
472
+ * @returns A new TableStyler instance with updated properties
473
+ */
474
+ update(properties: Partial<TableStylerProps<V>>): TableStyler<V> {
475
+ const {
476
+ dataFrame = this.dataFrame,
477
+ font = this.font,
478
+ border = this.border,
479
+ background = this.background,
480
+ dimension = this.dimension,
481
+ padding = this.padding,
482
+ margin = this.margin,
483
+ } = properties
484
+ return new TableStyler<V>(
485
+ dataFrame,
486
+ font,
487
+ border,
488
+ background,
489
+ dimension,
490
+ padding,
491
+ margin,
492
+ this.errors
493
+ )
494
+ }
495
+
496
+ withTableFont(font: Partial<TableFont>): TableStyler<V> {
497
+ const builder = this.copy()
498
+ builder.font = {...defaultTableFont, ...font}
499
+ return builder
500
+ }
501
+
502
+ /**
503
+ * Sets the background for the table.
504
+ * @param background The background settings to apply
505
+ * @returns A new TableStyler instance with the updated background
506
+ */
507
+ withTableBackground(background: Partial<Background>): TableStyler<V> {
508
+ const builder = this.copy()
509
+ builder.background = {...builder.background, ...background}
510
+ return builder
511
+ }
512
+
513
+ /**
514
+ * Sets the border for the table.
515
+ * @param border The border settings to apply
516
+ * @returns A new TableStyler instance with the updated border
517
+ */
518
+ withBorder(border: Partial<Border>): TableStyler<V> {
519
+ const builder = this.copy()
520
+ builder.border = {...builder.border, ...border}
521
+ return builder
522
+ }
523
+
524
+ /**
525
+ * Sets the dimensions for the table.
526
+ * @param width The width of the table
527
+ * @param height The height of the table
528
+ * @returns A new TableStyler instance with the updated dimensions
529
+ */
530
+ withDimensions(width: number, height: number): TableStyler<V> {
531
+ const builder = this.copy()
532
+ builder.dimension = {width, height}
533
+ return builder
534
+ }
535
+
536
+ /**
537
+ * Sets the padding for the table.
538
+ * @param padding The padding settings to apply
539
+ * @returns A new TableStyler instance with the updated padding
540
+ */
541
+ withPadding(padding: Partial<Padding>): TableStyler<V> {
542
+ const builder = this.copy()
543
+ builder.padding = {...builder.padding, ...padding}
544
+ return builder
545
+ }
546
+
547
+ /**
548
+ * Sets the margin for the table.
549
+ * @param margin The margin settings to apply
550
+ * @returns A new TableStyler instance with the updated margin
551
+ */
552
+ withMargin(margin: Partial<Margin>): TableStyler<V> {
553
+ const builder = this.copy()
554
+ builder.margin = {...builder.margin, ...margin}
555
+ return builder
556
+ }
557
+
558
+ /**
559
+ * Tags a row with a style.
560
+ * @param rowIndex The index of the row to tag
561
+ * @param tagStyleType The type of style to apply
562
+ * @param style The style value to apply
563
+ * @returns A Result containing a new TableStyler with the tagged row, or an error message
564
+ * @private
565
+ */
566
+ private tagRow<S extends TagValue>(rowIndex: number, tagStyleType: TableStyleType, style: S): Result<TableStyler<V>, string> {
567
+ return this.dataFrame.tagRow<S>(rowIndex, tagStyleType, style)
568
+ // when successfully tagged, make an updated copy of this builder with the new data-frame
569
+ .map(df => this.update({dataFrame: df}))
570
+ // when failed to tag, add to the errors
571
+ .onFailure(error => this.errors.push(error))
572
+ }
573
+
574
+ /**
575
+ * Tags a column with a style.
576
+ * @param columnIndex The index of the column to tag
577
+ * @param tagStyleType The type of style to apply
578
+ * @param style The style value to apply
579
+ * @returns A Result containing a new TableStyler with the tagged column, or an error message
580
+ * @private
581
+ */
582
+ private tagColumn<S extends TagValue>(columnIndex: number, tagStyleType: TableStyleType, style: S): Result<TableStyler<V>, string> {
583
+ return this.dataFrame.tagColumn<S>(columnIndex, tagStyleType, style)
584
+ // when successfully tagged, make an updated copy of this builder with the new data-frame
585
+ .map(df => this.update({dataFrame: df}))
586
+ // when failed to tag, add to the errors
587
+ .onFailure(error => this.errors.push(error))
588
+ }
589
+
590
+ /**
591
+ * Sets the style for the column header row.
592
+ * @param columnHeaderStyle The style to apply to the column header. Style properties that are not specified
593
+ * will be set to their default values.
594
+ * @param priority The priority of this style (higher values take precedence)
595
+ * @returns A new TableStyler instance with the column header style applied
596
+ */
597
+ withColumnHeaderStyle(
598
+ columnHeaderStyle: Partial<ColumnHeaderStyle> = defaultColumnHeaderStyle,
599
+ priority: number = Infinity
600
+ ): TableStyler<V> {
601
+ if (!TableData.hasColumnHeader(this.dataFrame)) {
602
+ this.errors.push("The column header style can only be supplied when the table data has a column header")
603
+ return this
604
+ }
605
+ // tag the row as a column header style, and if it fails, then return this (unmodified) builder
606
+ return this
607
+ .tagRow<Styling<ColumnHeaderStyle>>(
608
+ 0,
609
+ TableStyleType.COLUMN_HEADER,
610
+ stylingFor(columnHeaderStyle, defaultColumnHeaderStyle, priority)
611
+ )
612
+ .getOrElse(this)
613
+ }
614
+
615
+ /**
616
+ * Sets the style for the row header column.
617
+ * @param rowHeaderStyle The style to apply to the row header. Style properties that are not specified
618
+ * will be set to their default values.
619
+ * @param priority The priority of this style (higher values take precedence)
620
+ * @returns A new TableStyler instance with the row header style applied
621
+ */
622
+ withRowHeaderStyle(rowHeaderStyle: Partial<RowHeaderStyle>, priority: number = Infinity): TableStyler<V> {
623
+ if (!TableData.hasRowHeader(this.dataFrame)) {
624
+ this.errors.push("The row header style can only be supplied when the table data has row headers")
625
+ return this
626
+ }
627
+ // tag the column with the row header style, and if it fails, then return this (unmodified) builder
628
+ return this
629
+ .tagColumn<Styling<RowHeaderStyle>>(0, TableStyleType.ROW_HEADER, stylingFor(rowHeaderStyle, defaultRowHeaderStyle, priority))
630
+ .getOrElse(this)
631
+ }
632
+
633
+ /**
634
+ * Sets the style for the footer row.
635
+ * @param footerStyle The style to apply to the footer. Style properties that are not specified
636
+ * will be set to their default values.
637
+ * @param priority The priority of this style (higher values take precedence)
638
+ * @returns A new TableStyler instance with the footer style applied
639
+ */
640
+ withFooterStyle(footerStyle: Partial<FooterStyle>, priority: number = Infinity): TableStyler<V> {
641
+ if (!TableData.hasFooter(this.dataFrame)) {
642
+ this.errors.push("The footer style can only be supplied when the table data has a footer")
643
+ return this
644
+ }
645
+ // tag the row the footer style, and if it fails, then return this (unmodified) builder
646
+ const footerIndex = TableData.tableRowCount(this.dataFrame) - 1
647
+ return this
648
+ .tagRow<Styling<FooterStyle>>(footerIndex, TableStyleType.FOOTER, stylingFor(footerStyle, defaultFooterStyle, priority))
649
+ .getOrElse(this)
650
+ }
651
+
652
+ /**
653
+ * Sets the style for a specific row.
654
+ * @param rowIndex The index of the row to style
655
+ * @param rowStyle The style to apply to the row. Style properties that are not specified
656
+ * will be set to their default values.
657
+ * @param priority The priority of this style (higher values take precedence)
658
+ * @returns A new TableStyler instance with the row style applied
659
+ */
660
+ withRowStyle(rowIndex: number, rowStyle: Partial<RowStyle>, priority: number = 0): TableStyler<V> {
661
+ if (rowIndex < 0 || rowIndex >= TableData.tableRowCount(this.dataFrame)) {
662
+ this.errors.push(
663
+ `The row index, when setting a row-style, must be between 0 and ${TableData.tableRowCount(this.dataFrame) - 1}`
664
+ )
665
+ return this
666
+ }
667
+ // tag the row with a style, and if it fails, then return this (unmodified) builder
668
+ return this
669
+ .tagRow<Styling<RowStyle>>(rowIndex, TableStyleType.ROW, stylingFor(rowStyle, defaultRowStyle, priority))
670
+ .getOrElse(this)
671
+ }
672
+
673
+ withRowStyles(rowIndexes: Array<number>, rowStyle: Partial<RowStyle>, priority: number = 0): TableStyler<V> {
674
+ const indexes = rowIndexes.length > 0 ? rowIndexes : new Array(this.dataFrame.rowCount()).fill(0).map((_, i) => i)
675
+ return TableStyler.withRowStyles(this, indexes, rowStyle, priority)
676
+ }
677
+
678
+ private static withRowStyles<V>(
679
+ tableStyler: TableStyler<V>,
680
+ rowIndexes: Array<number>,
681
+ rowStyle: Partial<RowStyle>,
682
+ priority: number = 0
683
+ ): TableStyler<V> {
684
+ if (rowIndexes.length > 0) {
685
+ const rowIndex = rowIndexes.shift()
686
+ if (rowIndex != null) {
687
+ const styler = tableStyler.withRowStyle(rowIndex, rowStyle, priority)
688
+ return TableStyler.withRowStyles(styler, rowIndexes, rowStyle, priority)
689
+ }
690
+ }
691
+ return tableStyler
692
+ }
693
+
694
+ /**
695
+ * Sets the style for a specific column.
696
+ * @param columnIndex The index of the column to style
697
+ * @param columnStyle The style to apply to the column. Style properties that are not specified
698
+ * will be set to their default values.
699
+ * @param priority The priority of this style (higher values take precedence)
700
+ * @returns A new TableStyler instance with the column style applied
701
+ */
702
+ withColumnStyle(columnIndex: number, columnStyle: Partial<ColumnStyle>, priority: number = 0): TableStyler<V> {
703
+ if (columnIndex < 0 || columnIndex >= TableData.tableColumnCount(this.dataFrame)) {
704
+ this.errors.push(
705
+ `The column index, when setting a column-style, must be between 0 and ${TableData.tableColumnCount(this.dataFrame) - 1}`
706
+ )
707
+ return this
708
+ }
709
+ // tag the row with a style, and if it fails, then return this (unmodified) builder
710
+ return this
711
+ .tagColumn<Styling<ColumnStyle>>(columnIndex, TableStyleType.COLUMN, stylingFor(columnStyle, defaultColumnStyle, priority))
712
+ .getOrElse(this)
713
+ }
714
+
715
+ withColumnStyles(columnIndexes: Array<number>, columnStyle: Partial<ColumnStyle>, priority: number = 0): TableStyler<V> {
716
+ const indexes = columnIndexes.length > 0 ? columnIndexes : new Array(this.dataFrame.columnCount()).fill(0).map((_, i) => i)
717
+ return TableStyler.withColumnStyles(this, indexes, columnStyle, priority)
718
+ }
719
+
720
+ private static withColumnStyles<V>(
721
+ tableStyler: TableStyler<V>,
722
+ columnIndexes: Array<number>,
723
+ columnStyle: Partial<ColumnStyle>,
724
+ priority: number = 0
725
+ ): TableStyler<V> {
726
+ if (columnIndexes.length > 0) {
727
+ const columnIndex = columnIndexes.shift()
728
+ if (columnIndex != null) {
729
+ const styler = tableStyler.withColumnStyle(columnIndex, columnStyle, priority)
730
+ return TableStyler.withColumnStyles(styler, columnIndexes, columnStyle, priority)
731
+ }
732
+ }
733
+ return tableStyler
734
+ }
735
+
736
+ /**
737
+ * Sets the style for a specific cell.
738
+ * @param rowIndex The row index of the cell to style
739
+ * @param columnIndex The column index of the cell to style
740
+ * @param cellStyle The style to apply to the cell. Style properties that are not specified
741
+ * will be set to their default values.
742
+ * @param priority The priority of this style (higher values take precedence)
743
+ * @returns A new TableStyler instance with the cell style applied
744
+ */
745
+ withCellStyle(rowIndex: number, columnIndex: number, cellStyle: Partial<CellStyle>, priority: number = 0): TableStyler<V> {
746
+ if (rowIndex < 0 || rowIndex >= TableData.tableRowCount(this.dataFrame) ||
747
+ columnIndex < 0 || columnIndex >= TableData.tableColumnCount(this.dataFrame)) {
748
+ this.errors.push(
749
+ `The (row, column) indices, when setting a cell-style, must be in ` +
750
+ `([0, ${TableData.tableRowCount(this.dataFrame)}), [0, ${TableData.tableColumnCount(this.dataFrame)}))`
751
+ )
752
+ return this
753
+ }
754
+ // tag the cell with the cell-style
755
+ return this.dataFrame
756
+ .tagCell<Styling<CellStyle>>(rowIndex, columnIndex, TableStyleType.CELL, stylingFor(cellStyle, defaultCellStyle, priority))
757
+ // when successfully tagged, make an updated copy of this builder with the new data-frame
758
+ .map(df => this.update({dataFrame: df}))
759
+ // when failed to tag, add to the errors
760
+ .onFailure(error => this.errors.push(error))
761
+ // when failed, return this (unmodified) builder
762
+ .getOrElse(this)
763
+ }
764
+
765
+ withCellStyleWhen(
766
+ predicate: (value: V, rowIndex: number, columnIndex: number) => boolean,
767
+ cellStyle: Partial<CellStyle>,
768
+ priority: number = 0
769
+ ): TableStyler<V> {
770
+ return this.dataFrame
771
+ .tagCellWhen(predicate, TableStyleType.CELL, stylingFor(cellStyle, defaultCellStyle, priority))
772
+ // when successfully tagged, make an updated copy of this builder with the new data-frame
773
+ .map(df => this.update({dataFrame: df}))
774
+ // when failed to tag, add to the errors
775
+ .onFailure(error => this.errors.push(error))
776
+ // when failed, return this (unmodified) builder
777
+ .getOrElse(this)
778
+ }
779
+
780
+ /**
781
+ * Finalizes the styling process and creates a StyledTable instance.
782
+ * @returns A StyledTable instance with all the applied styles
783
+ */
784
+ styleTable(): StyledTable<V> {
785
+ return new StyledTable(
786
+ this.dataFrame,
787
+
788
+ this.font,
789
+
790
+ this.border,
791
+ this.background,
792
+
793
+ this.dimension,
794
+ this.padding,
795
+ this.margin,
796
+ )
797
+ }
798
+ }