chartforge 0.0.3 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anand Pilania
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -64,6 +64,18 @@
64
64
  - [Build Reference](#build-reference)
65
65
  - [Build outputs](#build-outputs)
66
66
  - [License](#license)
67
+ - [CLI Usage (`npx chartforge`)](#cli-usage-npx-chartforge)
68
+ - [Quick examples](#quick-examples)
69
+ - [Input sources](#input-sources)
70
+ - [Supported input formats](#supported-input-formats)
71
+ - [Output formats](#output-formats)
72
+ - [HTTP / API fetching](#http--api-fetching)
73
+ - [Live polling (`serve` command)](#live-polling-serve-command)
74
+ - [File watching (`watch` command)](#file-watching-watch-command)
75
+ - [All options](#all-options)
76
+ - [PNG export prerequisites](#png-export-prerequisites)
77
+ - [HttpAdapter (browser / library)](#httpadapter-browser--library)
78
+ - [CLI architecture](#cli-architecture)
67
79
 
68
80
  ---
69
81
 
@@ -1337,3 +1349,263 @@ npm run preview
1337
1349
  ## License
1338
1350
 
1339
1351
  MIT © Anand Pilania
1352
+
1353
+ ---
1354
+
1355
+ ## CLI Usage (`npx chartforge`)
1356
+
1357
+ ChartForge ships a fully-featured CLI. No installation required — just `npx`.
1358
+
1359
+ ```bash
1360
+ npx chartforge [command] [options]
1361
+ ```
1362
+
1363
+ ### Quick examples
1364
+
1365
+ ```bash
1366
+ # From a JSON file → HTML page (opens in browser)
1367
+ npx chartforge data.json --open
1368
+
1369
+ # CSV file → line chart as SVG
1370
+ npx chartforge sales.csv -t line --out svg -o chart.svg
1371
+
1372
+ # Inline data → terminal bar chart
1373
+ npx chartforge --data '[10,25,18,42,35]' -t bar --out terminal
1374
+
1375
+ # Pipe from stdin
1376
+ echo '[100,120,115,134]' | npx chartforge - --out terminal -t line
1377
+
1378
+ # From HTTP URL → HTML export
1379
+ npx chartforge --url https://api.example.com/data -o report.html --open
1380
+
1381
+ # Live terminal dashboard — polls every 3s
1382
+ npx chartforge serve --url https://api.example.com/metrics --interval 3 --out terminal
1383
+
1384
+ # Watch a file, re-render on change
1385
+ npx chartforge watch --input data.json --out html --open
1386
+ ```
1387
+
1388
+ ### Input sources
1389
+
1390
+ | Source | Flag | Example |
1391
+ | ----------- | ------------------------ | ------------------------------------ |
1392
+ | File | `--input` / positional | `chartforge data.json` |
1393
+ | stdin | `-` or no flag with pipe | `cat data.csv \| chartforge -` |
1394
+ | Inline JSON | `--data` | `--data '[1,2,3]'` |
1395
+ | HTTP URL | `--url` | `--url https://api.example.com/data` |
1396
+
1397
+ ### Supported input formats
1398
+
1399
+ ChartForge auto-detects format from file extension. Override with `--format`.
1400
+
1401
+ | Format | Auto-detect | Notes |
1402
+ | ------ | --------------- | ----------------------------------------------------------------------------- |
1403
+ | JSON | `.json` | ChartForge shape, arrays, key-value objects, arrays of objects |
1404
+ | CSV | `.csv` | First non-numeric column = labels. Multiple numeric columns = multiple series |
1405
+ | TSV | `.tsv` | Same as CSV but tab-delimited |
1406
+ | YAML | `.yaml`, `.yml` | Simple key:value only; for complex YAML convert to JSON first |
1407
+
1408
+ **Auto-detected JSON shapes:**
1409
+
1410
+ ```json
1411
+ // ChartForge native shape
1412
+ { "series": [{ "name": "Revenue", "data": [100, 120, 130] }], "labels": ["Jan","Feb","Mar"] }
1413
+
1414
+ // Plain array → single series
1415
+ [100, 120, 130, 145]
1416
+
1417
+ // Key-value object → labels + single series
1418
+ { "Jan": 100, "Feb": 120, "Mar": 130 }
1419
+
1420
+ // Array of objects → auto-detect label/value columns
1421
+ [{ "month": "Jan", "value": 100 }, { "month": "Feb", "value": 120 }]
1422
+ ```
1423
+
1424
+ ### Output formats
1425
+
1426
+ | Format | Flag | Notes |
1427
+ | -------- | ---------------- | ------------------------------------------------------------- |
1428
+ | HTML | `--out html` | Standalone HTML page with stats panel (default) |
1429
+ | SVG | `--out svg` | Raw SVG file, self-contained |
1430
+ | PNG | `--out png` | Requires `npm install sharp` or `npm install @resvg/resvg-js` |
1431
+ | Terminal | `--out terminal` | Unicode bar/line/pie charts + sparklines, no file created |
1432
+
1433
+ ```bash
1434
+ # HTML (default) — full standalone page
1435
+ npx chartforge data.json
1436
+
1437
+ # SVG — vector, embeddable anywhere
1438
+ npx chartforge data.json --out svg -o chart.svg
1439
+
1440
+ # PNG — raster image (install sharp first)
1441
+ npm install sharp
1442
+ npx chartforge data.json --out png -o chart.png
1443
+
1444
+ # Terminal — stays in your shell
1445
+ npx chartforge data.json --out terminal
1446
+ ```
1447
+
1448
+ ### HTTP / API fetching
1449
+
1450
+ ```bash
1451
+ # Simple GET
1452
+ npx chartforge --url https://api.example.com/data
1453
+
1454
+ # Extract nested path with --jq
1455
+ npx chartforge --url https://api.example.com/report --jq data.series
1456
+
1457
+ # POST with JSON body + auth header
1458
+ npx chartforge \
1459
+ --url https://api.example.com/analytics \
1460
+ --method POST \
1461
+ --headers '{"Authorization":"Bearer TOKEN","Content-Type":"application/json"}' \
1462
+ --body '{"range":"7d","metric":"revenue"}'
1463
+
1464
+ # CSV from HTTP
1465
+ npx chartforge --url https://data.example.com/export.csv --format csv -t line
1466
+ ```
1467
+
1468
+ ### Live polling (`serve` command)
1469
+
1470
+ ```bash
1471
+ # Terminal live dashboard — auto-refreshes
1472
+ npx chartforge serve \
1473
+ --url https://api.example.com/live \
1474
+ --interval 5 \
1475
+ --out terminal \
1476
+ -t line
1477
+
1478
+ # Save updated HTML every poll
1479
+ npx chartforge serve \
1480
+ --url https://api.example.com/metrics \
1481
+ --interval 10 \
1482
+ --out html \
1483
+ -o dashboard.html \
1484
+ --open
1485
+ ```
1486
+
1487
+ ### File watching (`watch` command)
1488
+
1489
+ ```bash
1490
+ # Re-render when the file changes
1491
+ npx chartforge watch --input data.json --out html --open
1492
+
1493
+ # Watch + terminal output
1494
+ npx chartforge watch --input data.json --out terminal -t line
1495
+ ```
1496
+
1497
+ ### All options
1498
+
1499
+ ```
1500
+ -i, --input <path> Input file path (JSON/CSV/TSV/YAML) or - for stdin
1501
+ -u, --url <url> Fetch data from HTTP/HTTPS URL
1502
+ -d, --data <json> Inline JSON data string
1503
+ -f, --format <fmt> Input format: json | csv | tsv | yaml
1504
+ --jq <path> Dot-path to extract from JSON (e.g. data.items)
1505
+ -X, --method <verb> HTTP method: GET | POST [GET]
1506
+ -H, --headers <json> JSON string of HTTP headers
1507
+ -b, --body <str> HTTP request body (for POST)
1508
+ --interval <sec> Poll interval in seconds for serve [5]
1509
+
1510
+ -t, --type <type> Chart type [column]
1511
+ --title <text> Chart title
1512
+ -w, --width <px> Width in pixels [800]
1513
+ -h, --height <px> Height in pixels [450]
1514
+ --theme <name> Theme: light | dark | neon [dark]
1515
+ -l, --labels <a,b,c> Comma-separated label override
1516
+
1517
+ -o, --output <path> Output file path (or - for stdout)
1518
+ --out <format> Output format: html | svg | png | terminal [html]
1519
+ --open Open output in browser after rendering
1520
+ --watch Watch input file and re-render on change
1521
+
1522
+ -v, --verbose Verbose output
1523
+ --no-color Disable ANSI colors
1524
+ --help Show help
1525
+ -V, --version Print version
1526
+ ```
1527
+
1528
+ ### PNG export prerequisites
1529
+
1530
+ PNG rendering is optional and requires one of:
1531
+
1532
+ ```bash
1533
+ # Option 1: sharp (best quality, most common)
1534
+ npm install sharp
1535
+
1536
+ # Option 2: @resvg/resvg-js (pure WASM, no native compilation)
1537
+ npm install @resvg/resvg-js
1538
+ ```
1539
+
1540
+ ### HttpAdapter (browser / library)
1541
+
1542
+ For polling HTTP endpoints inside a chart (not CLI), use the `HttpAdapter`:
1543
+
1544
+ ```ts
1545
+ import { ChartForge } from 'chartforge';
1546
+ import { HttpAdapter } from 'chartforge';
1547
+
1548
+ const chart = new ChartForge('#c', { type: 'line', data: { series: [] } });
1549
+
1550
+ chart.realTime.registerAdapter('http', HttpAdapter);
1551
+ chart.realTime.connect('http', {
1552
+ url: 'https://api.example.com/metrics',
1553
+ interval: 5, // seconds
1554
+ jq: 'data.series', // optional dot-path extractor
1555
+ transform: (raw) => ({ // optional custom transformer
1556
+ labels: raw.timestamps,
1557
+ series: [{ name: 'CPU', data: raw.cpu }],
1558
+ }),
1559
+ });
1560
+
1561
+ // Stop polling
1562
+ chart.realTime.disconnect('http');
1563
+ ```
1564
+
1565
+ ### CLI architecture
1566
+
1567
+ ```
1568
+ cli/
1569
+ ├── src/
1570
+ │ ├── index.ts # Entry point / npx binary
1571
+ │ ├── args.ts # Zero-dep argument parser
1572
+ │ ├── types.ts # CLI-specific types
1573
+ │ ├── logger.ts # ANSI-colored logger
1574
+ │ │
1575
+ │ ├── commands/
1576
+ │ │ ├── render.ts # render command (default)
1577
+ │ │ ├── watch.ts # watch command
1578
+ │ │ └── serve.ts # serve command (live poll)
1579
+ │ │
1580
+ │ ├── inputs/
1581
+ │ │ ├── parser.ts # JSON/CSV/TSV/YAML parsers
1582
+ │ │ ├── file.ts # File + stdin reader
1583
+ │ │ └── http.ts # HTTP fetcher + poll loop
1584
+ │ │
1585
+ │ ├── renderer/
1586
+ │ │ ├── svg.ts # Node.js SVG renderer (JSDOM)
1587
+ │ │ ├── png.ts # PNG via sharp/@resvg (optional)
1588
+ │ │ └── terminal.ts # Unicode bar/braille/sparkline renderer
1589
+ │ │
1590
+ │ └── outputs/
1591
+ │ ├── html.ts # Standalone HTML page generator
1592
+ │ └── writer.ts # File writer + browser opener
1593
+
1594
+ └── dist/
1595
+ └── chartforge.cjs # Bundled CLI binary (built by Vite)
1596
+ ```
1597
+
1598
+ Build the CLI:
1599
+
1600
+ ```bash
1601
+ npm run build:cli # → cli/dist/chartforge.cjs
1602
+ npm run build:all # lib + cli together
1603
+ ```
1604
+
1605
+ Test without building:
1606
+
1607
+ ```bash
1608
+ npm run cli:dev -- data.json --out terminal
1609
+ # or
1610
+ npx tsx cli/src/index.ts data.json --out terminal
1611
+ ```
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const BasePlugin = require("./BasePlugin-DcBTEJBs.cjs");
4
+ const misc = require("./misc-tOf-LXeZ.cjs");
5
+ class AxisPlugin extends BasePlugin.BasePlugin {
6
+ constructor(chart, cfg = {}) {
7
+ super(chart, cfg);
8
+ this._opts = misc.merge({
9
+ x: { enabled: true, label: "", fontSize: 11, tickLength: 5 },
10
+ y: { enabled: true, label: "", fontSize: 11, tickLength: 5, ticks: 5 }
11
+ }, cfg);
12
+ }
13
+ init() {
14
+ const c = this._chart;
15
+ this._group = misc.createSVGElement("g", { className: "cf-axis" });
16
+ c.svg.appendChild(this._group);
17
+ this._els.push(this._group);
18
+ this._draw();
19
+ c.on("beforeRender", () => this._draw());
20
+ }
21
+ _draw() {
22
+ const c = this._chart;
23
+ misc.removeChildren(this._group);
24
+ const vb = c.svg.getAttribute("viewBox").split(" ").map(Number);
25
+ const W = vb[2], H = vb[3];
26
+ const pad = c.config.padding ?? {};
27
+ const p = { top: pad.top ?? 40, right: pad.right ?? 40, bottom: pad.bottom ?? 60, left: pad.left ?? 60 };
28
+ if (this._opts.x?.enabled) this._drawX(W, H, p);
29
+ if (this._opts.y?.enabled) this._drawY(W, H, p);
30
+ }
31
+ _drawX(W, H, p) {
32
+ const c = this._chart;
33
+ const xo = this._opts.x;
34
+ const y = H - p.bottom;
35
+ const sx = p.left, ex = W - p.right;
36
+ this._group.appendChild(misc.createSVGElement("line", {
37
+ x1: sx,
38
+ y1: y,
39
+ x2: ex,
40
+ y2: y,
41
+ stroke: c.theme.axis.line,
42
+ "stroke-width": 1
43
+ }));
44
+ const labels = c.config.data.labels ?? [];
45
+ const step = (ex - sx) / (labels.length || 1);
46
+ labels.forEach((lbl, i) => {
47
+ const x = sx + step * i + step / 2;
48
+ this._group.appendChild(misc.createSVGElement("line", {
49
+ x1: x,
50
+ y1: y,
51
+ x2: x,
52
+ y2: y + (xo.tickLength ?? 5),
53
+ stroke: c.theme.axis.line,
54
+ "stroke-width": 1
55
+ }));
56
+ const t = misc.createSVGElement("text", {
57
+ x,
58
+ y: y + (xo.tickLength ?? 5) + 13,
59
+ fill: c.theme.axis.text,
60
+ "font-size": xo.fontSize ?? 11,
61
+ "text-anchor": "middle"
62
+ });
63
+ t.textContent = lbl;
64
+ this._group.appendChild(t);
65
+ });
66
+ if (xo.label) {
67
+ const t = misc.createSVGElement("text", {
68
+ x: (sx + ex) / 2,
69
+ y: H - 8,
70
+ fill: c.theme.text,
71
+ "font-size": (xo.fontSize ?? 11) + 2,
72
+ "text-anchor": "middle",
73
+ "font-weight": "bold"
74
+ });
75
+ t.textContent = xo.label;
76
+ this._group.appendChild(t);
77
+ }
78
+ }
79
+ // @ts-ignore
80
+ _drawY(W, H, p) {
81
+ const c = this._chart;
82
+ const yo = this._opts.y;
83
+ const x = p.left;
84
+ const sy = p.top, ey = H - p.bottom;
85
+ this._group.appendChild(misc.createSVGElement("line", {
86
+ x1: x,
87
+ y1: sy,
88
+ x2: x,
89
+ y2: ey,
90
+ stroke: c.theme.axis.line,
91
+ "stroke-width": 1
92
+ }));
93
+ const nticks = yo.ticks ?? 5;
94
+ const step = (ey - sy) / nticks;
95
+ const allVals = (c.config.data.series ?? []).flatMap((s) => s.data);
96
+ const maxVal = allVals.length ? Math.max(...allVals) : 100;
97
+ for (let i = 0; i <= nticks; i++) {
98
+ const ty = ey - i * step;
99
+ const val = (maxVal / nticks * i).toFixed(0);
100
+ this._group.appendChild(misc.createSVGElement("line", {
101
+ x1: x - (yo.tickLength ?? 5),
102
+ y1: ty,
103
+ x2: x,
104
+ y2: ty,
105
+ stroke: c.theme.axis.line,
106
+ "stroke-width": 1
107
+ }));
108
+ const t = misc.createSVGElement("text", {
109
+ x: x - (yo.tickLength ?? 5) - 5,
110
+ y: ty,
111
+ fill: c.theme.axis.text,
112
+ "font-size": yo.fontSize ?? 11,
113
+ "text-anchor": "end",
114
+ "dominant-baseline": "middle"
115
+ });
116
+ t.textContent = val;
117
+ this._group.appendChild(t);
118
+ }
119
+ if (yo.label) {
120
+ const mid = (sy + ey) / 2;
121
+ const t = misc.createSVGElement("text", {
122
+ x: 15,
123
+ y: mid,
124
+ fill: c.theme.text,
125
+ "font-size": (yo.fontSize ?? 11) + 2,
126
+ "text-anchor": "middle",
127
+ "font-weight": "bold",
128
+ transform: `rotate(-90,15,${mid})`
129
+ });
130
+ t.textContent = yo.label;
131
+ this._group.appendChild(t);
132
+ }
133
+ }
134
+ }
135
+ exports.AxisPlugin = AxisPlugin;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ class BasePlugin {
3
+ constructor(_chart, _cfg) {
4
+ this._chart = _chart;
5
+ this._cfg = _cfg;
6
+ this._els = [];
7
+ }
8
+ destroy() {
9
+ this._els.forEach((el) => el.parentNode?.removeChild(el));
10
+ this._els.length = 0;
11
+ }
12
+ }
13
+ exports.BasePlugin = BasePlugin;