chartforge 0.0.2 → 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 +21 -0
- package/README.md +272 -0
- package/cli/dist/AxisPlugin-Cge0pH5P.cjs +135 -0
- package/cli/dist/BasePlugin-DcBTEJBs.cjs +13 -0
- package/cli/dist/ChartForge-BMKdVldJ.cjs +1108 -0
- package/cli/dist/GridPlugin-C90wMQsN.cjs +69 -0
- package/cli/dist/builtins-5HYzyd_B.cjs +44 -0
- package/cli/dist/chartforge.cjs +3 -0
- package/cli/dist/index-BtGYSrj-.cjs +240 -0
- package/cli/dist/misc-tOf-LXeZ.cjs +52 -0
- package/cli/dist/png-By3-3Jat.cjs +61 -0
- package/cli/dist/render-OAxwCzdV.cjs +94 -0
- package/cli/dist/serve-74PsUgLE.cjs +58 -0
- package/cli/dist/watch-Dv_eG4Wh.cjs +34 -0
- package/cli/dist/writer-CRUdthqh.cjs +697 -0
- package/dist/plugins.umd.cjs +1 -0
- package/dist/themes.umd.cjs +1 -0
- package/package.json +88 -68
|
@@ -0,0 +1,69 @@
|
|
|
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 GridPlugin extends BasePlugin.BasePlugin {
|
|
6
|
+
constructor(chart, cfg = {}) {
|
|
7
|
+
super(chart, cfg);
|
|
8
|
+
const c = chart;
|
|
9
|
+
this._opts = misc.merge({
|
|
10
|
+
enabled: true,
|
|
11
|
+
x: { enabled: true, color: c.theme.axis.grid, strokeWidth: 1, dashArray: "2,2" },
|
|
12
|
+
y: { enabled: true, color: c.theme.axis.grid, strokeWidth: 1, dashArray: "2,2", ticks: 5 }
|
|
13
|
+
}, cfg);
|
|
14
|
+
}
|
|
15
|
+
init() {
|
|
16
|
+
if (!this._opts.enabled) return;
|
|
17
|
+
const c = this._chart;
|
|
18
|
+
this._group = misc.createSVGElement("g", { className: "cf-grid" });
|
|
19
|
+
c.mainGroup.insertBefore(this._group, c.mainGroup.firstChild);
|
|
20
|
+
this._els.push(this._group);
|
|
21
|
+
this._draw();
|
|
22
|
+
c.on("beforeRender", () => this._draw());
|
|
23
|
+
}
|
|
24
|
+
_draw() {
|
|
25
|
+
const c = this._chart;
|
|
26
|
+
misc.removeChildren(this._group);
|
|
27
|
+
const vb = c.svg.getAttribute("viewBox").split(" ").map(Number);
|
|
28
|
+
const W = vb[2], H = vb[3];
|
|
29
|
+
const pad = c.config.padding ?? {};
|
|
30
|
+
const p = { top: pad.top ?? 40, right: pad.right ?? 40, bottom: pad.bottom ?? 60, left: pad.left ?? 60 };
|
|
31
|
+
const sx = p.left, ex = W - p.right;
|
|
32
|
+
const sy = p.top, ey = H - p.bottom;
|
|
33
|
+
if (this._opts.y?.enabled) {
|
|
34
|
+
const nticks = this._opts.y.ticks ?? 5;
|
|
35
|
+
const step = (ey - sy) / nticks;
|
|
36
|
+
for (let i = 0; i <= nticks; i++) {
|
|
37
|
+
const y = ey - i * step;
|
|
38
|
+
this._group.appendChild(misc.createSVGElement("line", {
|
|
39
|
+
x1: sx,
|
|
40
|
+
y1: y,
|
|
41
|
+
x2: ex,
|
|
42
|
+
y2: y,
|
|
43
|
+
stroke: this._opts.y.color ?? "#ccc",
|
|
44
|
+
"stroke-width": this._opts.y.strokeWidth ?? 1,
|
|
45
|
+
"stroke-dasharray": this._opts.y.dashArray ?? "2,2",
|
|
46
|
+
opacity: "0.5"
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (this._opts.x?.enabled) {
|
|
51
|
+
const labels = c.config.data.labels ?? [];
|
|
52
|
+
const step = (ex - sx) / (labels.length || 1);
|
|
53
|
+
for (let i = 0; i <= labels.length; i++) {
|
|
54
|
+
const x = sx + step * i;
|
|
55
|
+
this._group.appendChild(misc.createSVGElement("line", {
|
|
56
|
+
x1: x,
|
|
57
|
+
y1: sy,
|
|
58
|
+
x2: x,
|
|
59
|
+
y2: ey,
|
|
60
|
+
stroke: this._opts.x.color ?? "#ccc",
|
|
61
|
+
"stroke-width": this._opts.x.strokeWidth ?? 1,
|
|
62
|
+
"stroke-dasharray": this._opts.x.dashArray ?? "2,2",
|
|
63
|
+
opacity: "0.5"
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.GridPlugin = GridPlugin;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const lightTheme = {
|
|
4
|
+
background: "#ffffff",
|
|
5
|
+
foreground: "#000000",
|
|
6
|
+
grid: "#e5e5e5",
|
|
7
|
+
text: "#333333",
|
|
8
|
+
textSecondary: "#666666",
|
|
9
|
+
colors: ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"],
|
|
10
|
+
tooltip: { background: "#ffffff", text: "#000000", border: "#e5e5e5", shadow: "rgba(0,0,0,0.1)" },
|
|
11
|
+
legend: { text: "#333333", hover: "#000000" },
|
|
12
|
+
axis: { line: "#d1d5db", text: "#6b7280", grid: "#f3f4f6" }
|
|
13
|
+
};
|
|
14
|
+
const darkTheme = {
|
|
15
|
+
background: "#1a1a1a",
|
|
16
|
+
foreground: "#ffffff",
|
|
17
|
+
grid: "#333333",
|
|
18
|
+
text: "#e5e5e5",
|
|
19
|
+
textSecondary: "#999999",
|
|
20
|
+
colors: ["#60a5fa", "#f87171", "#34d399", "#fbbf24", "#a78bfa", "#f472b6", "#22d3ee", "#a3e635"],
|
|
21
|
+
tooltip: { background: "#2a2a2a", text: "#ffffff", border: "#444444", shadow: "rgba(0,0,0,0.3)" },
|
|
22
|
+
legend: { text: "#e5e5e5", hover: "#ffffff" },
|
|
23
|
+
axis: { line: "#404040", text: "#9ca3af", grid: "#262626" }
|
|
24
|
+
};
|
|
25
|
+
const neonTheme = {
|
|
26
|
+
background: "#0a0a0a",
|
|
27
|
+
foreground: "#00ffff",
|
|
28
|
+
grid: "#1a1a2e",
|
|
29
|
+
text: "#00ffff",
|
|
30
|
+
textSecondary: "#ff00ff",
|
|
31
|
+
colors: ["#00ffff", "#ff00ff", "#ffff00", "#00ff00", "#ff0080", "#8000ff", "#ff8000", "#0080ff"],
|
|
32
|
+
tooltip: { background: "#1a1a2e", text: "#00ffff", border: "#00ffff", shadow: "rgba(0,255,255,0.3)" },
|
|
33
|
+
legend: { text: "#00ffff", hover: "#ff00ff" },
|
|
34
|
+
axis: { line: "#1a1a2e", text: "#00ffff", grid: "#16161f" }
|
|
35
|
+
};
|
|
36
|
+
const BUILT_IN_THEMES = {
|
|
37
|
+
light: lightTheme,
|
|
38
|
+
dark: darkTheme,
|
|
39
|
+
neon: neonTheme
|
|
40
|
+
};
|
|
41
|
+
exports.BUILT_IN_THEMES = BUILT_IN_THEMES;
|
|
42
|
+
exports.darkTheme = darkTheme;
|
|
43
|
+
exports.lightTheme = lightTheme;
|
|
44
|
+
exports.neonTheme = neonTheme;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const FLAGS = {
|
|
3
|
+
// Input
|
|
4
|
+
"input": { alias: "i", type: "string", desc: "Input file path (JSON/CSV/TSV/YAML) or - for stdin" },
|
|
5
|
+
"url": { alias: "u", type: "string", desc: "Fetch data from HTTP/HTTPS URL" },
|
|
6
|
+
"data": { alias: "d", type: "string", desc: "Inline JSON data string" },
|
|
7
|
+
"format": { alias: "f", type: "string", desc: "Input format: json | csv | tsv | yaml" },
|
|
8
|
+
"jq": { type: "string", desc: "Dot-path to extract from JSON response (e.g. data.items)" },
|
|
9
|
+
"method": { alias: "X", type: "string", desc: "HTTP method: GET | POST (default: GET)", default: "GET" },
|
|
10
|
+
"headers": { alias: "H", type: "string", desc: "JSON string of HTTP headers" },
|
|
11
|
+
"body": { alias: "b", type: "string", desc: "HTTP request body (for POST)" },
|
|
12
|
+
"interval": { type: "number", desc: "Poll interval in seconds for --serve (default: 5)", default: 5 },
|
|
13
|
+
// Chart
|
|
14
|
+
"type": { alias: "t", type: "string", desc: "Chart type: column|bar|line|pie|donut|scatter|stackedColumn|stackedBar|funnel|heatmap|candlestick", default: "column" },
|
|
15
|
+
"title": { type: "string", desc: "Chart title" },
|
|
16
|
+
"width": { alias: "w", type: "number", desc: "Chart width in pixels (default: 800)", default: 800 },
|
|
17
|
+
"height": { alias: "h", type: "number", desc: "Chart height in pixels (default: 450)", default: 450 },
|
|
18
|
+
"theme": { type: "string", desc: "Theme: light | dark | neon (default: dark)", default: "dark" },
|
|
19
|
+
"labels": { alias: "l", type: "string", desc: "Comma-separated label override" },
|
|
20
|
+
// Output
|
|
21
|
+
"output": { alias: "o", type: "string", desc: "Output file path (default: auto-named in cwd)" },
|
|
22
|
+
"out": { type: "string", desc: "Output format: html | svg | png | terminal (default: html)", default: "html" },
|
|
23
|
+
"open": { type: "boolean", desc: "Open output in browser after rendering", default: false },
|
|
24
|
+
"watch": { type: "boolean", desc: "Watch input file and re-render on change", default: false },
|
|
25
|
+
// Misc
|
|
26
|
+
"verbose": { alias: "v", type: "boolean", desc: "Verbose output", default: false },
|
|
27
|
+
"no-color": { type: "boolean", desc: "Disable ANSI colors", default: false },
|
|
28
|
+
"help": { type: "boolean", desc: "Show this help message", default: false },
|
|
29
|
+
"version": { alias: "V", type: "boolean", desc: "Print version", default: false }
|
|
30
|
+
};
|
|
31
|
+
const ALIAS_MAP = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const [name, def] of Object.entries(FLAGS)) {
|
|
33
|
+
if (def.alias) ALIAS_MAP.set(def.alias, name);
|
|
34
|
+
}
|
|
35
|
+
function parseArgs(argv) {
|
|
36
|
+
const args = argv.slice(2);
|
|
37
|
+
const opts = {};
|
|
38
|
+
let command = "render";
|
|
39
|
+
for (const [key, def] of Object.entries(FLAGS)) {
|
|
40
|
+
if (def.default !== void 0) opts[key] = def.default;
|
|
41
|
+
}
|
|
42
|
+
let i = 0;
|
|
43
|
+
while (i < args.length) {
|
|
44
|
+
const arg = args[i];
|
|
45
|
+
if (!arg.startsWith("-")) {
|
|
46
|
+
if (["render", "serve", "watch", "help", "version"].includes(arg)) {
|
|
47
|
+
command = arg;
|
|
48
|
+
} else {
|
|
49
|
+
opts["input"] = arg;
|
|
50
|
+
}
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg.includes("=")) {
|
|
55
|
+
const [rawKey2, ...rest] = arg.replace(/^-+/, "").split("=");
|
|
56
|
+
const key2 = ALIAS_MAP.get(rawKey2) ?? rawKey2;
|
|
57
|
+
const value = rest.join("=");
|
|
58
|
+
const def2 = FLAGS[key2];
|
|
59
|
+
opts[key2] = def2?.type === "number" ? Number(value) : value;
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const rawKey = arg.replace(/^-+/, "");
|
|
64
|
+
const key = ALIAS_MAP.get(rawKey) ?? rawKey;
|
|
65
|
+
const def = FLAGS[key];
|
|
66
|
+
if (!def) {
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (def.type === "boolean") {
|
|
71
|
+
opts[key] = true;
|
|
72
|
+
i++;
|
|
73
|
+
} else {
|
|
74
|
+
const value = args[i + 1];
|
|
75
|
+
opts[key] = def.type === "number" ? Number(value) : value;
|
|
76
|
+
i += 2;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (opts["no-color"]) process.env["NO_COLOR"] = "1";
|
|
80
|
+
return {
|
|
81
|
+
command: command === "help" ? "help" : command,
|
|
82
|
+
opts
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function printHelp(version) {
|
|
86
|
+
const c2 = (code, s) => process.stdout.isTTY ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
87
|
+
console.log();
|
|
88
|
+
console.log(c2("36;1", " ⬡ ChartForge CLI") + c2("90", ` v${version}`));
|
|
89
|
+
console.log(c2("2", " SVG/PNG/HTML/Terminal chart generator"));
|
|
90
|
+
console.log();
|
|
91
|
+
console.log(c2("1", " Usage"));
|
|
92
|
+
console.log(c2("36", " npx chartforge") + " [command] [options]");
|
|
93
|
+
console.log(c2("36", " chartforge") + " <file> [options]");
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(c2("1", " Commands"));
|
|
96
|
+
const cmds = [
|
|
97
|
+
["render", "Render a chart from file, URL, or stdin (default)"],
|
|
98
|
+
["serve", "Poll a URL and live-refresh output"],
|
|
99
|
+
["watch", "Watch a file and re-render on change"],
|
|
100
|
+
["help", "Show this help message"],
|
|
101
|
+
["version", "Print version number"]
|
|
102
|
+
];
|
|
103
|
+
for (const [cmd, desc] of cmds) {
|
|
104
|
+
console.log(` ${c2("36", cmd.padEnd(10))} ${desc}`);
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(c2("1", " Options"));
|
|
108
|
+
for (const [name, def] of Object.entries(FLAGS)) {
|
|
109
|
+
if (name === "help" || name === "version") continue;
|
|
110
|
+
const alias = def.alias ? `-${def.alias}, ` : " ";
|
|
111
|
+
const flag = `${alias}--${name}`;
|
|
112
|
+
const dflt = def.default !== void 0 && def.default !== false ? c2("90", ` [${def.default}]`) : "";
|
|
113
|
+
console.log(` ${c2("33", flag.padEnd(24))} ${def.desc}${dflt}`);
|
|
114
|
+
}
|
|
115
|
+
console.log();
|
|
116
|
+
console.log(c2("1", " Examples"));
|
|
117
|
+
const examples = [
|
|
118
|
+
["# From file", ""],
|
|
119
|
+
["chartforge data.json", ""],
|
|
120
|
+
["chartforge data.csv -t line -o chart.html --open", ""],
|
|
121
|
+
["", ""],
|
|
122
|
+
["# From stdin", ""],
|
|
123
|
+
["echo '[10,20,30]' | chartforge - -t bar --out terminal", ""],
|
|
124
|
+
["cat sales.csv | chartforge - -t column --theme neon --out svg", ""],
|
|
125
|
+
["", ""],
|
|
126
|
+
["# From HTTP URL", ""],
|
|
127
|
+
["chartforge --url https://api.example.com/data --jq data.series", ""],
|
|
128
|
+
["chartforge --url https://api/csv --format csv -t line", ""],
|
|
129
|
+
["", ""],
|
|
130
|
+
["# Live terminal dashboard", ""],
|
|
131
|
+
["chartforge serve --url https://api.example.com/metrics --interval 3 --out terminal", ""],
|
|
132
|
+
["", ""],
|
|
133
|
+
["# Watch file", ""],
|
|
134
|
+
["chartforge watch --input data.json --out html --open", ""],
|
|
135
|
+
["", ""],
|
|
136
|
+
["# Export PNG (requires sharp)", ""],
|
|
137
|
+
["chartforge data.json --out png --output chart.png", ""],
|
|
138
|
+
["", ""],
|
|
139
|
+
["# Pipe SVG to file", ""],
|
|
140
|
+
["chartforge data.json --out svg -o - > chart.svg", ""],
|
|
141
|
+
["", ""],
|
|
142
|
+
["# Inline data", ""],
|
|
143
|
+
[`chartforge --data '{"series":[{"data":[1,2,3]}]}' --out terminal`, ""],
|
|
144
|
+
["", ""],
|
|
145
|
+
["# HTTP POST with auth", ""],
|
|
146
|
+
[`chartforge --url https://api/data --method POST --headers '{"Authorization":"Bearer TOKEN"}' --body '{"range":"7d"}'`, ""]
|
|
147
|
+
];
|
|
148
|
+
for (const [ex] of examples) {
|
|
149
|
+
if (!ex) {
|
|
150
|
+
console.log();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (ex.startsWith("#")) {
|
|
154
|
+
console.log(c2("90", ` ${ex}`));
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
console.log(c2("2", ` $ `) + c2("36", ex));
|
|
158
|
+
}
|
|
159
|
+
console.log();
|
|
160
|
+
}
|
|
161
|
+
const USE_COLOR = !process.env["NO_COLOR"] && process.stdout.isTTY;
|
|
162
|
+
const c = {
|
|
163
|
+
reset: "\x1B[0m",
|
|
164
|
+
bold: "\x1B[1m",
|
|
165
|
+
dim: "\x1B[2m",
|
|
166
|
+
cyan: "\x1B[36m",
|
|
167
|
+
green: "\x1B[32m",
|
|
168
|
+
yellow: "\x1B[33m",
|
|
169
|
+
red: "\x1B[31m",
|
|
170
|
+
gray: "\x1B[90m"
|
|
171
|
+
};
|
|
172
|
+
const paint = (code, text) => USE_COLOR ? `${code}${text}${c.reset}` : text;
|
|
173
|
+
const logger = {
|
|
174
|
+
prefix: paint(c.cyan + c.bold, "⬡ ChartForge"),
|
|
175
|
+
info: (msg) => console.log(`${logger.prefix} ${paint(c.gray, "›")} ${msg}`),
|
|
176
|
+
success: (msg) => console.log(`${logger.prefix} ${paint(c.green, "✓")} ${paint(c.green, msg)}`),
|
|
177
|
+
warn: (msg) => console.warn(`${logger.prefix} ${paint(c.yellow, "⚠")} ${paint(c.yellow, msg)}`),
|
|
178
|
+
error: (msg, err) => {
|
|
179
|
+
console.error(`${logger.prefix} ${paint(c.red, "✗")} ${paint(c.red, msg)}`);
|
|
180
|
+
if (err) console.error(paint(c.dim, String(err instanceof Error ? err.stack : err)));
|
|
181
|
+
},
|
|
182
|
+
step: (msg) => console.log(` ${paint(c.cyan, "→")} ${msg}`),
|
|
183
|
+
dim: (msg) => console.log(paint(c.dim, ` ${msg}`)),
|
|
184
|
+
blank: () => console.log(),
|
|
185
|
+
// Banner shown on startup
|
|
186
|
+
banner: () => {
|
|
187
|
+
console.log();
|
|
188
|
+
console.log(paint(c.cyan + c.bold, [
|
|
189
|
+
" ╔═══════════════════════════╗",
|
|
190
|
+
" ║ ⬡ C H A R T F O R G E ║",
|
|
191
|
+
" ║ CLI Visualization Tool ║",
|
|
192
|
+
" ╚═══════════════════════════╝"
|
|
193
|
+
].join("\n")));
|
|
194
|
+
console.log(paint(c.dim, " SVG • PNG • HTML • Terminal"));
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const VERSION = "1.0.0";
|
|
199
|
+
async function main() {
|
|
200
|
+
const { command, opts } = parseArgs(process.argv);
|
|
201
|
+
if (command === "version" || opts.version) {
|
|
202
|
+
console.log(`chartforge/${VERSION}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (command === "help" || opts.help) {
|
|
206
|
+
printHelp(VERSION);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
logger.banner();
|
|
210
|
+
try {
|
|
211
|
+
switch (command) {
|
|
212
|
+
case "serve": {
|
|
213
|
+
const { runServe } = await Promise.resolve().then(() => require("./serve-74PsUgLE.cjs"));
|
|
214
|
+
await runServe(opts);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "watch": {
|
|
218
|
+
const { runWatch } = await Promise.resolve().then(() => require("./watch-Dv_eG4Wh.cjs"));
|
|
219
|
+
await runWatch(opts);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
default: {
|
|
223
|
+
const { runRender } = await Promise.resolve().then(() => require("./render-OAxwCzdV.cjs"));
|
|
224
|
+
await runRender(opts);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
logger.error(
|
|
230
|
+
err instanceof Error ? err.message : String(err),
|
|
231
|
+
opts.verbose ? err : void 0
|
|
232
|
+
);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
main().catch((err) => {
|
|
237
|
+
console.error("[ChartForge]", err);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|
|
240
|
+
exports.logger = logger;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
function createSVGElement(tag, attrs = {}) {
|
|
3
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
4
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
5
|
+
if (key === "className") {
|
|
6
|
+
el.setAttribute("class", String(value));
|
|
7
|
+
} else if (key.startsWith("on") && typeof value === "function") {
|
|
8
|
+
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
9
|
+
} else {
|
|
10
|
+
el.setAttribute(key, String(value));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return el;
|
|
14
|
+
}
|
|
15
|
+
function removeChildren(el) {
|
|
16
|
+
while (el.firstChild) el.removeChild(el.firstChild);
|
|
17
|
+
}
|
|
18
|
+
function polarToCartesian(cx, cy, r, angleDeg) {
|
|
19
|
+
const rad = (angleDeg - 90) * Math.PI / 180;
|
|
20
|
+
return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
|
|
21
|
+
}
|
|
22
|
+
function uid() {
|
|
23
|
+
return `cf-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
24
|
+
}
|
|
25
|
+
function debounce(fn, delay) {
|
|
26
|
+
let id;
|
|
27
|
+
return (...args) => {
|
|
28
|
+
clearTimeout(id);
|
|
29
|
+
id = setTimeout(() => fn(...args), delay);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function merge(target, ...sources) {
|
|
33
|
+
for (const src of sources) {
|
|
34
|
+
if (!src) continue;
|
|
35
|
+
for (const key of Object.keys(src)) {
|
|
36
|
+
const sv = src[key];
|
|
37
|
+
const tv = target[key];
|
|
38
|
+
if (sv && typeof sv === "object" && !Array.isArray(sv) && tv && typeof tv === "object" && !Array.isArray(tv)) {
|
|
39
|
+
merge(tv, sv);
|
|
40
|
+
} else if (sv !== void 0) {
|
|
41
|
+
target[key] = sv;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return target;
|
|
46
|
+
}
|
|
47
|
+
exports.createSVGElement = createSVGElement;
|
|
48
|
+
exports.debounce = debounce;
|
|
49
|
+
exports.merge = merge;
|
|
50
|
+
exports.polarToCartesian = polarToCartesian;
|
|
51
|
+
exports.removeChildren = removeChildren;
|
|
52
|
+
exports.uid = uid;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
25
|
+
const index = require("./index-BtGYSrj-.cjs");
|
|
26
|
+
async function renderToPNG(svgResult, scale = 2) {
|
|
27
|
+
try {
|
|
28
|
+
const sharp = (await import("sharp")).default;
|
|
29
|
+
const buf = Buffer.from(svgResult.svg, "utf8");
|
|
30
|
+
return await sharp(buf).resize(svgResult.width * scale, svgResult.height * scale).png({ compressionLevel: 9 }).toBuffer();
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const { Resvg } = await import("@resvg/resvg-js");
|
|
35
|
+
const resvg = new Resvg(svgResult.svg, {
|
|
36
|
+
fitTo: { mode: "width", value: svgResult.width * scale }
|
|
37
|
+
});
|
|
38
|
+
const img = resvg.render();
|
|
39
|
+
return img.asPng();
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
index.logger.warn(
|
|
43
|
+
"PNG export requires one of: sharp, @resvg/resvg-js\n Install with: npm install sharp\n Or: npm install @resvg/resvg-js"
|
|
44
|
+
);
|
|
45
|
+
throw new Error("No PNG renderer available");
|
|
46
|
+
}
|
|
47
|
+
async function hasPNGSupport() {
|
|
48
|
+
try {
|
|
49
|
+
await import("sharp");
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await import("@resvg/resvg-js");
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
exports.hasPNGSupport = hasPNGSupport;
|
|
61
|
+
exports.renderToPNG = renderToPNG;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const writer = require("./writer-CRUdthqh.cjs");
|
|
6
|
+
const index = require("./index-BtGYSrj-.cjs");
|
|
7
|
+
async function readStdin() {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
let data = "";
|
|
10
|
+
process.stdin.setEncoding("utf8");
|
|
11
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
12
|
+
process.stdin.on("end", () => resolve(data));
|
|
13
|
+
process.stdin.on("error", reject);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function readFile(filePath) {
|
|
17
|
+
return fs.readFileSync(filePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
function detectFormat(filePath) {
|
|
20
|
+
const ext = path.extname(filePath).toLowerCase().slice(1);
|
|
21
|
+
return ext || "json";
|
|
22
|
+
}
|
|
23
|
+
async function loadFromFile(filePath, format) {
|
|
24
|
+
const raw = filePath === "-" ? await readStdin() : readFile(filePath);
|
|
25
|
+
const fmt = format ?? detectFormat(filePath);
|
|
26
|
+
return writer.parseInput(raw, fmt);
|
|
27
|
+
}
|
|
28
|
+
async function runRender(opts) {
|
|
29
|
+
const outFmt = opts.out ?? "html";
|
|
30
|
+
const verbose = opts.verbose ?? false;
|
|
31
|
+
let data;
|
|
32
|
+
if (opts.url) {
|
|
33
|
+
if (verbose) index.logger.step(`Fetching from URL: ${opts.url}`);
|
|
34
|
+
data = await writer.fetchData({
|
|
35
|
+
url: opts.url,
|
|
36
|
+
method: opts.method,
|
|
37
|
+
headers: opts.headers ? JSON.parse(opts.headers) : void 0,
|
|
38
|
+
body: opts.body,
|
|
39
|
+
jq: opts.jq
|
|
40
|
+
});
|
|
41
|
+
} else if (opts.data) {
|
|
42
|
+
if (verbose) index.logger.step("Parsing inline data");
|
|
43
|
+
data = writer.parseJSON(opts.data, opts.jq);
|
|
44
|
+
} else if (opts.input) {
|
|
45
|
+
if (verbose) index.logger.step(`Reading file: ${opts.input}`);
|
|
46
|
+
data = await loadFromFile(opts.input, opts.format);
|
|
47
|
+
} else {
|
|
48
|
+
if (verbose) index.logger.step("Reading from stdin");
|
|
49
|
+
data = await loadFromFile("-", opts.format);
|
|
50
|
+
}
|
|
51
|
+
if (verbose) {
|
|
52
|
+
index.logger.dim(`Loaded ${data.series.length} series, ${data.series[0]?.data?.length ?? 0} points`);
|
|
53
|
+
}
|
|
54
|
+
if (outFmt === "terminal") {
|
|
55
|
+
const rendered = writer.renderToTerminal(data, {
|
|
56
|
+
type: opts.type,
|
|
57
|
+
title: opts.title,
|
|
58
|
+
width: opts.width
|
|
59
|
+
});
|
|
60
|
+
writer.writeStdout(rendered);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (verbose) index.logger.step("Rendering SVG");
|
|
64
|
+
const svgResult = await writer.renderToSVG(data, opts);
|
|
65
|
+
let outPath = opts.output;
|
|
66
|
+
if (outFmt === "svg") {
|
|
67
|
+
const dest2 = outPath ?? writer.defaultFilename("svg");
|
|
68
|
+
if (dest2 === "-") {
|
|
69
|
+
writer.writeStdout(svgResult.svg);
|
|
70
|
+
} else {
|
|
71
|
+
writer.writeOutput(svgResult.svg, dest2);
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (outFmt === "png") {
|
|
76
|
+
const { renderToPNG } = await Promise.resolve().then(() => require("./png-By3-3Jat.cjs"));
|
|
77
|
+
if (verbose) index.logger.step("Converting to PNG");
|
|
78
|
+
const pngBuf = await renderToPNG(svgResult);
|
|
79
|
+
const dest2 = outPath ?? writer.defaultFilename("png");
|
|
80
|
+
writer.writeOutput(pngBuf, dest2);
|
|
81
|
+
if (opts.open) await writer.openInBrowser(dest2);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (verbose) index.logger.step("Generating HTML page");
|
|
85
|
+
const html = writer.generateHTML(svgResult, data, opts);
|
|
86
|
+
const dest = outPath ?? writer.defaultFilename("html");
|
|
87
|
+
if (dest === "-") {
|
|
88
|
+
writer.writeStdout(html);
|
|
89
|
+
} else {
|
|
90
|
+
writer.writeOutput(html, dest);
|
|
91
|
+
if (opts.open) await writer.openInBrowser(dest);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.runRender = runRender;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const writer = require("./writer-CRUdthqh.cjs");
|
|
4
|
+
const index = require("./index-BtGYSrj-.cjs");
|
|
5
|
+
async function runServe(opts) {
|
|
6
|
+
if (!opts.url) {
|
|
7
|
+
index.logger.error("--serve requires --url <endpoint>");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const interval = opts.interval ?? 5;
|
|
11
|
+
const outFmt = opts.out ?? "terminal";
|
|
12
|
+
index.logger.info(`Live mode — polling ${opts.url} every ${interval}s`);
|
|
13
|
+
index.logger.dim(`Format: ${outFmt} | Ctrl+C to stop`);
|
|
14
|
+
index.logger.blank();
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
process.on("SIGINT", () => {
|
|
17
|
+
index.logger.blank();
|
|
18
|
+
index.logger.info("Stopped.");
|
|
19
|
+
controller.abort();
|
|
20
|
+
process.exit(0);
|
|
21
|
+
});
|
|
22
|
+
await writer.pollData(
|
|
23
|
+
{
|
|
24
|
+
url: opts.url,
|
|
25
|
+
method: opts.method,
|
|
26
|
+
headers: opts.headers ? JSON.parse(opts.headers) : void 0,
|
|
27
|
+
body: opts.body,
|
|
28
|
+
jq: opts.jq
|
|
29
|
+
},
|
|
30
|
+
interval,
|
|
31
|
+
async (data, tick) => {
|
|
32
|
+
if (outFmt === "terminal") {
|
|
33
|
+
if (tick > 0) process.stdout.write("\x1B[2J\x1B[H");
|
|
34
|
+
const rendered = writer.renderToTerminal(data, {
|
|
35
|
+
type: opts.type,
|
|
36
|
+
title: `${opts.title ?? "Live Chart"} — tick ${tick + 1}`,
|
|
37
|
+
width: opts.width
|
|
38
|
+
});
|
|
39
|
+
process.stdout.write(rendered);
|
|
40
|
+
} else {
|
|
41
|
+
const svgResult = await writer.renderToSVG(data, opts);
|
|
42
|
+
const dest = opts.output ?? writer.defaultFilename(outFmt === "html" ? "html" : outFmt);
|
|
43
|
+
if (outFmt === "html") {
|
|
44
|
+
writer.writeOutput(writer.generateHTML(svgResult, data, opts), dest);
|
|
45
|
+
} else if (outFmt === "svg") {
|
|
46
|
+
writer.writeOutput(svgResult.svg, dest);
|
|
47
|
+
} else if (outFmt === "png") {
|
|
48
|
+
const { renderToPNG } = await Promise.resolve().then(() => require("./png-By3-3Jat.cjs"));
|
|
49
|
+
writer.writeOutput(await renderToPNG(svgResult), dest);
|
|
50
|
+
}
|
|
51
|
+
if (tick === 0 && opts.open) await writer.openInBrowser(dest);
|
|
52
|
+
index.logger.info(`Tick ${tick + 1} — saved to ${dest}`);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
controller.signal
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
exports.runServe = runServe;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const render = require("./render-OAxwCzdV.cjs");
|
|
6
|
+
const index = require("./index-BtGYSrj-.cjs");
|
|
7
|
+
async function runWatch(opts) {
|
|
8
|
+
const filePath = opts.input;
|
|
9
|
+
if (!filePath || filePath === "-") {
|
|
10
|
+
index.logger.error("--watch requires --input <file>");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const abs = path.resolve(filePath);
|
|
14
|
+
index.logger.info(`Watching ${abs} for changes…`);
|
|
15
|
+
index.logger.dim("Press Ctrl+C to stop");
|
|
16
|
+
index.logger.blank();
|
|
17
|
+
await render.runRender({ ...opts, open: opts.open }).catch(
|
|
18
|
+
(err) => index.logger.error("Render failed", err)
|
|
19
|
+
);
|
|
20
|
+
let debounceTimer = null;
|
|
21
|
+
fs.watch(abs, { persistent: true }, (event) => {
|
|
22
|
+
if (event !== "change") return;
|
|
23
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
24
|
+
debounceTimer = setTimeout(async () => {
|
|
25
|
+
index.logger.info(`File changed — re-rendering…`);
|
|
26
|
+
await render.runRender({ ...opts, open: false }).catch(
|
|
27
|
+
(err) => index.logger.error("Re-render failed", err)
|
|
28
|
+
);
|
|
29
|
+
}, 200);
|
|
30
|
+
});
|
|
31
|
+
await new Promise(() => {
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
exports.runWatch = runWatch;
|