svg-table 0.0.2 → 0.1.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.
package/CHANGES.md ADDED
@@ -0,0 +1,9 @@
1
+ # change log
2
+
3
+ ## version 0.1.1
4
+
5
+ Fixed bug where the offset for the row was determined by the whether there was a row-header, rather than whether there was a column-header. And the offset for the column was determined by the whether there was a column-header, rather than whether there was a row-header.
6
+
7
+ ## version 0.1.0
8
+
9
+ The initial release.
package/README.md CHANGED
@@ -1,3 +1,449 @@
1
1
  # SVG Table
2
2
 
3
- Docs to come.
3
+ A tiny TypeScript library to turn tabular data into a styled SVG table. It separates three concerns:
4
+ - Data: describe your table structure (data, headers, footer) with TableData.
5
+ - Formatting: convert raw values into display strings with TableFormatter.
6
+ - Styling + Rendering: define look-and-feel with TableStyler and render into an SVG via createTable.
7
+
8
+ SVG Table is framework-agnostic and works with any environment where you can access an <svg> element (vanilla JS/TS, React, Svelte, etc.).
9
+
10
+ > See [the SVG table example](https://github.com/robphilipp/svg-table-example) for an example of creating a table with SVG Table.
11
+
12
+ ## Table of Contents
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Core Concepts](#core-concepts)
16
+ - [TableData](#tabledata)
17
+ - [TableFormatter](#tableformatter)
18
+ - [TableStyler](#tablestyler)
19
+ - [Rendering to SVG](#rendering-to-svg)
20
+ - [Examples](#examples)
21
+ - [1) Data with a column header](#1-data-with-a-column-header)
22
+ - [2) Column + row headers, custom formatters](#2-column--row-headers-custom-formatters)
23
+ - [3) Overriding specific cells](#3-overriding-specific-cells)
24
+ - [4) Styling the table](#4-styling-the-table)
25
+ - [API Surface (high level)](#api-surface-high-level)
26
+ - [Method Reference](#method-reference)
27
+ - [TableData methods](#tabledata-methods)
28
+ - [TableFormatter methods](#tableformatter-methods)
29
+ - [TableStyler methods](#tablestyler-methods)
30
+ - [Rendering methods](#rendering-methods)
31
+ - [Styling helpers](#styling-helpers)
32
+ - [FAQ](#faq)
33
+ - [License](#license)
34
+
35
+
36
+ ## Installation
37
+
38
+ [(toc)](#table-of-contents)
39
+
40
+ Install from npm. TypeScript types are included.
41
+
42
+ - npm: npm install svg-table d3 data-frame-ts
43
+ - pnpm: pnpm add svg-table d3 data-frame-ts
44
+ - yarn: yarn add svg-table d3 data-frame-ts
45
+
46
+ Peer libraries used in examples:
47
+ - data-frame-ts: Simple immutable 2D data container with tagging.
48
+ - d3: Only the selection utilities are used when you need them; the renderer accepts a native SVG element.
49
+
50
+ ## Quick Start
51
+
52
+ [(toc)](#table-of-contents)
53
+
54
+ This example builds a small table, formats numbers and dates, styles it, and renders into an existing <svg id="app"> element.
55
+
56
+ ```ts
57
+ // 1) Raw data (mixed types)
58
+ const data = DataFrame.from<(string | number | Date)>([
59
+ [new Date(2021, 1, 1, 1), 12345, 'gnm-f234', 123.45, 4],
60
+ [new Date(2021, 1, 2, 2), 23456, 'gnm-g234', 23.45, 5],
61
+ ]).getOrThrow()
62
+
63
+ const columnHeader = ['Date-Time', 'Customer ID', 'Product ID', 'Purchase Price', 'Amount']
64
+
65
+ // 2) Describe the table structure
66
+ const withHeader = TableData.fromDataFrame(data)
67
+ .withColumnHeader(columnHeader)
68
+ .getOrThrow()
69
+
70
+ // 3) Format values for display
71
+ const formatted = TableFormatter.fromTableData(withHeader)
72
+ // ensure the header row uses the default formatter (highest priority)
73
+ .addRowFormatter(0, defaultFormatter, Infinity)
74
+ // column-wise formatters (note column indexes refer to the table, not just data)
75
+ .flatMap(tf => tf.addColumnFormatter(0, v => (v as Date).toLocaleDateString()))
76
+ .flatMap(tf => tf.addColumnFormatter(1, v => defaultFormatter(v)))
77
+ .flatMap(tf => tf.addColumnFormatter(3, v => `$ ${(v as number).toFixed(2)}`))
78
+ .flatMap(tf => tf.addColumnFormatter(4, v => `${(v as number).toFixed(0)}`))
79
+ .flatMap(tf => tf.formatTable())
80
+ .getOrThrow()
81
+
82
+ // 4) Style the table (use defaults or customize)
83
+ const styled = TableStyler.fromTableData(formatted)
84
+ .withDimensions(600, 200)
85
+ .styleTable()
86
+ .getOrThrow()
87
+
88
+ // 5) Render to SVG at position (x=20, y=20)
89
+ const svg = document.getElementById('app') as unknown as SVGSVGElement
90
+ createTable(styled, svg, 'example-1', (w, h) => [20, 20])
91
+ .getOrThrow()
92
+ ```
93
+
94
+
95
+ ## Core Concepts
96
+
97
+ [(toc)](#table-of-contents)
98
+
99
+ ### TableData
100
+
101
+ [(toc)](#table-of-contents)
102
+
103
+ Represents your table’s structure over a DataFrame:
104
+ - Add column and row headers, and footers
105
+ - Ask for counts and sections of the table (data, headers, footer)
106
+
107
+ Key methods (return a Result so you can chain with .flatMap and finish with .getOrThrow()):
108
+ - TableData.fromDataFrame(df)
109
+ - withColumnHeader(header: string[], formatting = defaultFormatting)
110
+ - withRowHeader(header: (string|number)[], formatting = defaultFormatting, columnHeaderProvider?, footerProvider?)
111
+ - withFooter(footer: (string|number)[], formatting = defaultFormatting, rowHeaderProvider?)
112
+ - hasColumnHeader(), hasRowHeader(), hasFooter()
113
+ - tableRowCount(), tableColumnCount()
114
+ - data(), columnHeader(), rowHeader(), footer()
115
+
116
+ ### TableFormatter
117
+
118
+ [(toc)](#table-of-contents)
119
+
120
+ Transforms raw cell values into display strings. You assign formatters at different scopes with optional priorities. Higher priority wins for a given cell.
121
+
122
+ Scopes:
123
+ - Row-level: addRowFormatter(rowIndex, formatter, priority?)
124
+ - Column-level: addColumnFormatter(columnIndex, formatter, priority?)
125
+ - Cell-level: addCellFormatter(rowIndex, columnIndex, formatter, priority?)
126
+
127
+ Utilities:
128
+ - defaultFormatter: converts anything to string sensibly
129
+ - formatTable(): Result<TableData<string>, string>
130
+
131
+ Tip: When both row and column headers exist, remember that indexes are for the full table including headers.
132
+
133
+ ### TableStyler
134
+
135
+ [(toc)](#table-of-contents)
136
+
137
+ Assigns visual styles (font, padding, margins, borders, per-row/per-column/per-cell styles). Styling is also priority-based internally so the most specific style wins.
138
+
139
+ Useful entry points:
140
+ - TableStyler.fromTableData(tableData)
141
+ - withTableFont(font), withTableBackground(bg), withBorder(border)
142
+ - withDimensions(width, height), withPadding({top,left,bottom,right}), withMargin(...)
143
+ - withRowStyle(i, style, priority?), withColumnStyle(j, style, priority?), withCellStyle(i, j, style, priority?)
144
+ - withCellStyleWhen(predicate, style, priority?)
145
+ - withColumnHeaderStyle(style, priority?), withRowHeaderStyle(style, priority?), withFooterStyle(style, priority?)
146
+ - styleTable(): Result<StyledTable, string>
147
+
148
+ ### Rendering to SVG
149
+
150
+ [(toc)](#table-of-contents)
151
+
152
+ Use createTable(styledTable, svgElement, uniqueTableId, coordinates)
153
+
154
+ - styledTable: result of TableStyler.styleTable().getOrThrow()
155
+ - svgElement: an existing SVGSVGElement
156
+ - uniqueTableId: used to generate element ids
157
+ - coordinates: either a fixed [x, y] or a function (width, height) => [x, y]
158
+
159
+ Helper:
160
+ - tableId(uniqueTableId) -> the id of the root <g> group added to your svg
161
+
162
+
163
+ ## Examples
164
+
165
+ [(toc)](#table-of-contents)
166
+
167
+ ### 1) Data with a column header
168
+
169
+ [(toc)](#table-of-contents)
170
+
171
+ ```ts
172
+ const data = DataFrame.from<number | string>([
173
+ ['Widget A', 12.34],
174
+ ['Widget B', 56.7],
175
+ ]).getOrThrow()
176
+
177
+ const td = TableData.fromDataFrame(data)
178
+ .withColumnHeader(['Product', 'Price'])
179
+ .getOrThrow()
180
+ ```
181
+
182
+ ### 2) Column + row headers, custom formatters
183
+
184
+ [(toc)](#table-of-contents)
185
+
186
+ ```ts
187
+ const df = DataFrame.from<(string | number | Date)>([
188
+ [new Date(2021, 1, 1), 12345, 123.45],
189
+ [new Date(2021, 1, 2), 23456, 23.45],
190
+ ]).getOrThrow()
191
+
192
+ const tableData = TableData.fromDataFrame(df)
193
+ .withColumnHeader(['Date', 'Customer', 'Price'])
194
+ .flatMap(td => td.withRowHeader([1, 2]))
195
+ .getOrThrow()
196
+
197
+ const formatted = TableFormatter.fromTableData(tableData)
198
+ .addRowFormatters([0], defaultFormatter, Infinity)
199
+ .flatMap(tf => tf.addColumnFormatter(1, v => v.toString()))
200
+ .flatMap(tf => tf.addColumnFormatter(2, v => `$ ${(v as number).toFixed(2)}`))
201
+ .flatMap(tf => tf.formatTable())
202
+ .getOrThrow()
203
+ ```
204
+
205
+ ### 3) Overriding specific cells
206
+
207
+ [(toc)](#table-of-contents)
208
+
209
+ ```ts
210
+ const overridden = TableFormatter.fromTableData(formatted)
211
+ .addCellFormatter(1, 2, v => `${(v as number).toFixed(2)}`, 1000) // strong override
212
+ .flatMap(tf => tf.formatTable())
213
+ .getOrThrow()
214
+ ```
215
+
216
+ ### 4) Styling the table
217
+
218
+ [(toc)](#table-of-contents)
219
+
220
+ ```ts
221
+ const styled = TableStyler.fromTableData(overridden)
222
+ .withTableFont({ ...defaultTableFont(), size: '12px', weight: '600' })
223
+ .withTableBackground({ color: '#fff' })
224
+ .withPadding({ ...defaultTablePadding(), top: 8, left: 8, right: 8, bottom: 8 })
225
+ .withBorder({ ...defaultBorder(), color: '#999', width: 1 })
226
+ .withDimensions(480, 160)
227
+ .withRowStyle(0, { backgroundColor: '#f5f5f5' }) // header row, if present
228
+ .styleTable()
229
+ .getOrThrow()
230
+
231
+ // Render at (x, y) = (24, 24)
232
+ const svg = document.querySelector('svg#app') as unknown as SVGSVGElement
233
+ createTable(styled, svg, 'styled-demo', (w, h) => [24, 24])
234
+ .getOrThrow()
235
+ ```
236
+
237
+
238
+ ## API Surface (high level)
239
+
240
+ [(toc)](#table-of-contents)
241
+
242
+ This package re-exports the primary types and helpers from index.ts:
243
+
244
+ - TableData, TableTagType
245
+ - TableFormatter, defaultFormatter, isFormattingTag, defaultFormatting, TableFormatterType
246
+ - TableStyler, StyledTable
247
+ - createTable, elementInfoFrom, tableId
248
+ - Styling primitives and defaults: Styling, TableStylerProps, TableFont, Background, Padding, Margin,
249
+ BorderElement, Border, Dimension, ColumnStyle, RowStyle, CellStyle, ColumnHeaderStyle, RowHeaderStyle, FooterStyle,
250
+ TextAlignment, VerticalTextAlignment, Stylings, and their default* functions
251
+ - d3 type aliases: GroupSelection, TextSelection, RectSelection, LineSelection, BorderSelection
252
+
253
+ Use your editor’s type hints for full details, or read the source files:
254
+ - TableData.ts
255
+ - TableFormatter.ts
256
+ - TableStyler.ts
257
+ - tableSvg.ts
258
+ - stylings.ts
259
+
260
+
261
+ ## Method Reference
262
+
263
+ [(toc)](#table-of-contents)
264
+
265
+ ### TableData methods
266
+
267
+ [(toc)](#table-of-contents)
268
+
269
+ - fromDataFrame(df)
270
+ - Create a TableData from a DataFrame of values (strings, numbers, dates, etc.).
271
+ - Example:
272
+ ```ts
273
+ const df = DataFrame.from([['A', 1], ['B', 2]]).getOrThrow()
274
+ const td = TableData.fromDataFrame(df)
275
+ ```
276
+ - withColumnHeader(header: string[], formatting = defaultFormatting)
277
+ - Attach a column header row. Returns Result<TableData<...>, string>.
278
+ - Example:
279
+ ```ts
280
+ const td2 = td.withColumnHeader(['Name', 'Qty']).getOrThrow()
281
+ ```
282
+ - withRowHeader(header: (string|number)[], formatting = defaultFormatting, columnHeaderProvider?, footerProvider?)
283
+ - Attach a dedicated row header column. If a column header or footer already exists, use the providers so the new tags can reference each other under the hood.
284
+ - Example:
285
+ ```ts
286
+ const td3 = td2.withRowHeader([1, 2]).getOrThrow()
287
+ ```
288
+ - withFooter(footer: (string|number)[], formatting = defaultFormatting, rowHeaderProvider?)
289
+ - Attach a footer row. Works with or without row headers.
290
+ - Example:
291
+ ```ts
292
+ const td4 = td3.withFooter(['Total', 3]).getOrThrow()
293
+ ```
294
+ - hasColumnHeader() / hasRowHeader() / hasFooter()
295
+ - Booleans over the current table structure.
296
+ - Example:
297
+ ```ts
298
+ if (td4.hasFooter()) { /* ... */ }
299
+ ```
300
+ - dataRowCount() / dataColumnCount()
301
+ - The counts of the underlying data only (no headers/footers).
302
+ - tableRowCount() / tableColumnCount()
303
+ - The counts including any headers and/or footer.
304
+ - data()
305
+ - Returns Result<DataFrame<V>, string> of the data section only.
306
+ - columnHeader(includeRowHeader = false)
307
+ - Returns Result<string[]|number[], string> of the column header. When includeRowHeader is true and a row header exists, the left-most row header label(s) are included.
308
+ - rowHeader(includeColumnHeader = false, includeFooter = false)
309
+ - Returns Result<(string|number)[], string> of the row header column; optionally include the column header cell and/or footer cell for that column.
310
+ - footer(includeRowHeader = false)
311
+ - Returns Result<(string|number)[], string> of the footer row; optionally include the row header cell for that row.
312
+ - unwrapDataFrame()
313
+ - Returns the underlying DataFrame (unsafe; for advanced use where you manage tags yourself).
314
+
315
+
316
+ ### TableFormatter methods
317
+
318
+ [(toc)](#table-of-contents)
319
+
320
+ - fromTableData(tableData) / fromDataFrame(dataFrame)
321
+ - Build a TableFormatter around an existing table or raw data.
322
+ - Example:
323
+ ```ts
324
+ const tf = TableFormatter.fromTableData(td3)
325
+ ```
326
+ - addColumnFormatter(columnIndex, formatter, priority = 0)
327
+ - Assign a formatter to a column index in the full table coordinate space (including headers). Higher priority wins.
328
+ - Example:
329
+ ```ts
330
+ const tf2 = tf.addColumnFormatter(1, v => `$ ${(v as number).toFixed(2)}`)
331
+ ```
332
+ - addColumnFormatters(columnIndexes: number[], formatter, priority = 0)
333
+ - Convenience for adding the same formatter to multiple columns.
334
+ - Example:
335
+ ```ts
336
+ const tf3 = tf.flatMap(t => t.addColumnFormatters([1,2], v => v.toString()))
337
+ ```
338
+ - addRowFormatter(rowIndex, formatter, priority = 0)
339
+ - Assign a formatter to a specific row (e.g., the column header row at index 0).
340
+ - Example:
341
+ ```ts
342
+ const tfHdr = tf.addRowFormatter(0, defaultFormatter, Infinity)
343
+ ```
344
+ - addRowFormatters(rowIndexes: number[], formatter, priority = 0)
345
+ - Convenience to format multiple rows.
346
+ - addCellFormatter(rowIndex, columnIndex, formatter, priority = 0)
347
+ - Most specific override for a single cell.
348
+ - Example:
349
+ ```ts
350
+ const tfCell = tf.addCellFormatter(3, 2, v => (v as string).toUpperCase(), 1000)
351
+ ```
352
+ - addCellFormatters(cellIndexes: [row, col][], formatter, priority = 0)
353
+ - Apply the same cell-override to a list of cells.
354
+ - formatTable()
355
+ - Applies all formatters and returns Result<TableData<string>, string>.
356
+ - Example:
357
+ ```ts
358
+ const formatted = tfHdr.flatMap(t => t.formatTable()).getOrThrow()
359
+ ```
360
+ - formatTableInto(mapper)
361
+ - Advanced: Format into a custom element type by mapping each text cell into your own structure.
362
+
363
+ Utilities (exported): defaultFormatter, defaultFormatting, isFormattingTag, TableFormatterType (enum-ish marker for tags).
364
+
365
+
366
+ ### TableStyler methods
367
+
368
+ [(toc)](#table-of-contents)
369
+
370
+ - fromTableData(tableData) / fromDataFrame(dataFrame)
371
+ - Start configuring styles for a table.
372
+ - withTableFont(font) / withTableBackground(bg) / withBorder(border)
373
+ - Set global font, background, and border defaults.
374
+ - Example:
375
+ ```ts
376
+ const ts = TableStyler.fromTableData(formatted)
377
+ .withTableFont({ ...defaultTableFont(), size: '12px' })
378
+ .withTableBackground({ color: '#fff' })
379
+ .withBorder({ ...defaultBorder(), color: '#ddd', width: 1 })
380
+ ```
381
+ - withDimensions(width, height) / withPadding(padding) / withMargin(margin)
382
+ - Set overall layout metrics for the table and surrounding whitespace.
383
+ - withRowStyle(rowIndex, style, priority = 0) / withRowStyles(indexes, style, priority)
384
+ - Apply styles to header/data/footer rows (use table coordinates). Higher priority wins.
385
+ - withColumnStyle(columnIndex, style, priority = 0) / withColumnStyles(indexes, style, priority)
386
+ - Apply styles to whole columns.
387
+ - withCellStyle(rowIndex, columnIndex, style, priority = 0)
388
+ - Most specific style override for a single cell.
389
+ - withCellStyleWhen(predicate, style, priority = 0)
390
+ - Conditional styling; predicate receives (value, rowIndex, columnIndex) in table coordinates.
391
+ - Example:
392
+ ```ts
393
+ const styled = ts
394
+ .withCellStyleWhen((v, i, j) => j === 2 && Number(v) > 100, { color: '#d00' }, 10)
395
+ .styleTable()
396
+ .getOrThrow()
397
+ ```
398
+ - withColumnHeaderStyle(style, priority) / withRowHeaderStyle(style, priority) / withFooterStyle(style, priority)
399
+ - Section-specific styles that participate in the same priority system.
400
+ - styleTable()
401
+ - Finalize styles and produce Result<StyledTable, string>.
402
+
403
+ StyledTable (result) exposes getters used by the renderer: tableFont(), tableBackground(), tablePadding(), rowStyleFor(), columnStyleFor(), cellStyleFor(), and flags hasColumnHeader()/hasRowHeader()/hasFooter().
404
+
405
+
406
+ ### Rendering methods
407
+
408
+ [(toc)](#table-of-contents)
409
+
410
+ - createTable(styledTable, svgElement, uniqueTableId, coordinates)
411
+ - Render the styled table into a provided SVGSVGElement, adding a <g id="svg-table-group-..."> group. Coordinates can be [x, y] or a function of (width, height).
412
+ - Example:
413
+ ```ts
414
+ const svg = document.querySelector('svg#app') as unknown as SVGSVGElement
415
+ createTable(styled, svg, 'orders', (w, h) => [20, 20]).getOrThrow()
416
+ ```
417
+ - tableId(uniqueTableId)
418
+ - Helper that returns the id of the root group for a given table name.
419
+ - elementInfoFrom(textSel, cellSel, borderSel, style)
420
+ - Low-level helper used internally; exposed for tooling/tests.
421
+
422
+
423
+ ### Styling helpers
424
+
425
+ [(toc)](#table-of-contents)
426
+
427
+ - Defaults: defaultTableFont(), defaultTableBackground(), defaultTablePadding(), defaultTableMargin(), defaultBorder(), defaultDimension(), defaultColumnStyle(), defaultRowStyle(), defaultCellStyle(), defaultColumnHeaderStyle(), defaultRowHeaderStyle(), defaultFooterStyle().
428
+ - Example font override:
429
+ ```ts
430
+ const font = { ...defaultTableFont(), family: 'Inter, sans-serif', size: '14px' }
431
+ ```
432
+ - stylingFor(style)
433
+ - Wrap a plain style object into an internal Styling tag for use with tagRow/tagColumn APIs (you rarely need this directly; prefer withRowStyle/withColumnStyle/withCellStyle).
434
+ - TableStyleType
435
+ - Internal discriminator for style tags; useful if you explore raw tags.
436
+
437
+
438
+ ## FAQ
439
+
440
+ [(toc)](#table-of-contents)
441
+
442
+ - Why Results? Methods return Result<T, string> so you can safely compose operations (.map/.flatMap) and collect errors instead of throwing immediately. Finish a chain with .getOrThrow() when you want to surface errors.
443
+ - Do I need d3 to render? createTable accepts a native SVGSVGElement. d3 is only used internally for convenient selection/manipulation; you don’t have to use d3 elsewhere in your app.
444
+ - How are indexes counted when headers exist? Row/column indexes are for the full table including headers. For example, if you add a row header, data columns shift by one in TableFormatter.
445
+
446
+
447
+ ## License
448
+
449
+ MIT License. See LICENSE for details.