pipechart 1.0.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.
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ // Sparkline renderer — single-line inline chart using Unicode block chars
4
+
5
+ var ansi = require('../ansi');
6
+ var scale = require('../scale');
7
+
8
+ // Unicode block elements from lowest to highest
9
+ var SPARKS = [
10
+ '\u2581', // ▁ 1/8
11
+ '\u2582', // ▂ 2/8
12
+ '\u2583', // ▃ 3/8
13
+ '\u2584', // ▄ 4/8
14
+ '\u2585', // ▅ 5/8
15
+ '\u2586', // ▆ 6/8
16
+ '\u2587', // ▇ 7/8
17
+ '\u2588' // █ 8/8
18
+ ];
19
+
20
+ /**
21
+ * Convert a single normalized value [0,1] to a spark character.
22
+ * @param {number} normalized — value in [0, 1]
23
+ * @returns {string}
24
+ */
25
+ function toSparkChar(normalized) {
26
+ var idx = Math.round(normalized * (SPARKS.length - 1));
27
+ if (idx < 0) idx = 0;
28
+ if (idx >= SPARKS.length) idx = SPARKS.length - 1;
29
+ return SPARKS[idx];
30
+ }
31
+
32
+ /**
33
+ * Render a sparkline.
34
+ *
35
+ * @param {object} opts
36
+ * @param {number[]} opts.values — data values
37
+ * @param {number} opts.width — max terminal width (chars)
38
+ * @param {string} [opts.color] — ANSI color name
39
+ * @param {string} [opts.title] — optional title prefix
40
+ * @returns {string} — single line (or title + line)
41
+ */
42
+ function render(opts) {
43
+ var values = opts.values || [];
44
+ var width = opts.width || 80;
45
+ var color = opts.color || 'green';
46
+ var title = opts.title || null;
47
+
48
+ if (values.length === 0) {
49
+ return ansi.colorize('(no data)', 'dim');
50
+ }
51
+
52
+ var minVal = scale.min(values);
53
+ var maxVal = scale.max(values);
54
+
55
+ // If we have more values than width, downsample by averaging buckets
56
+ var displayValues = values;
57
+ if (values.length > width) {
58
+ displayValues = downsample(values, width);
59
+ }
60
+
61
+ var spark = '';
62
+ for (var i = 0; i < displayValues.length; i++) {
63
+ var norm = scale.normalize(displayValues[i], minVal, maxVal);
64
+ spark += toSparkChar(norm);
65
+ }
66
+
67
+ var colored = ansi.colorize(spark, color);
68
+
69
+ var minStr = ansi.dim(scale.formatNum(minVal));
70
+ var maxStr = ansi.dim(scale.formatNum(maxVal));
71
+
72
+ var lines = [];
73
+ if (title) {
74
+ lines.push(ansi.bold(ansi.colorize(title, color)));
75
+ }
76
+ lines.push(colored + ' ' + minStr + ' \u2013 ' + maxStr);
77
+
78
+ return lines.join('\n');
79
+ }
80
+
81
+ /**
82
+ * Downsample an array to targetLen by averaging buckets.
83
+ * @param {number[]} values
84
+ * @param {number} targetLen
85
+ * @returns {number[]}
86
+ */
87
+ function downsample(values, targetLen) {
88
+ var result = [];
89
+ var bucketSize = values.length / targetLen;
90
+ for (var i = 0; i < targetLen; i++) {
91
+ var start = Math.floor(i * bucketSize);
92
+ var end = Math.floor((i + 1) * bucketSize);
93
+ if (end > values.length) end = values.length;
94
+ var sum = 0;
95
+ for (var j = start; j < end; j++) sum += values[j];
96
+ result.push(sum / (end - start));
97
+ }
98
+ return result;
99
+ }
100
+
101
+ module.exports = { render: render, toSparkChar: toSparkChar, downsample: downsample };
package/lib/scale.js ADDED
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ // Normalization and scaling math helpers
4
+
5
+ /**
6
+ * Find the minimum value in an array of numbers.
7
+ * @param {number[]} values
8
+ * @returns {number}
9
+ */
10
+ function min(values) {
11
+ var m = Infinity;
12
+ for (var i = 0; i < values.length; i++) {
13
+ if (values[i] < m) m = values[i];
14
+ }
15
+ return m;
16
+ }
17
+
18
+ /**
19
+ * Find the maximum value in an array of numbers.
20
+ * @param {number[]} values
21
+ * @returns {number}
22
+ */
23
+ function max(values) {
24
+ var m = -Infinity;
25
+ for (var i = 0; i < values.length; i++) {
26
+ if (values[i] > m) m = values[i];
27
+ }
28
+ return m;
29
+ }
30
+
31
+ /**
32
+ * Normalize a value from [minVal, maxVal] to [0, 1].
33
+ * Returns 0 if minVal === maxVal (flat line).
34
+ * @param {number} value
35
+ * @param {number} minVal
36
+ * @param {number} maxVal
37
+ * @returns {number}
38
+ */
39
+ function normalize(value, minVal, maxVal) {
40
+ if (maxVal === minVal) return 0;
41
+ return (value - minVal) / (maxVal - minVal);
42
+ }
43
+
44
+ /**
45
+ * Scale a normalized value [0,1] to [0, range].
46
+ * @param {number} normalized — value in [0, 1]
47
+ * @param {number} range — target integer range (e.g. terminal width)
48
+ * @returns {number} — integer in [0, range]
49
+ */
50
+ function scaleToRange(normalized, range) {
51
+ return Math.round(normalized * range);
52
+ }
53
+
54
+ /**
55
+ * Map a value from [minVal, maxVal] directly to [0, range].
56
+ * @param {number} value
57
+ * @param {number} minVal
58
+ * @param {number} maxVal
59
+ * @param {number} range
60
+ * @returns {number}
61
+ */
62
+ function mapToRange(value, minVal, maxVal, range) {
63
+ return scaleToRange(normalize(value, minVal, maxVal), range);
64
+ }
65
+
66
+ /**
67
+ * Compute a "nice" tick step for axis labels.
68
+ * @param {number} range — the data range (max - min)
69
+ * @param {number} ticks — desired number of ticks
70
+ * @returns {number}
71
+ */
72
+ function niceStep(range, ticks) {
73
+ if (range === 0) return 1;
74
+ var rawStep = range / ticks;
75
+ var magnitude = Math.pow(10, Math.floor(Math.log(rawStep) / Math.LN10));
76
+ var normalized = rawStep / magnitude;
77
+ var nice;
78
+ if (normalized < 1.5) nice = 1;
79
+ else if (normalized < 3) nice = 2;
80
+ else if (normalized < 7) nice = 5;
81
+ else nice = 10;
82
+ return nice * magnitude;
83
+ }
84
+
85
+ /**
86
+ * Format a number for axis display — trim unnecessary decimals.
87
+ * @param {number} value
88
+ * @param {number} [decimals=2]
89
+ * @returns {string}
90
+ */
91
+ function formatNum(value, decimals) {
92
+ if (decimals === undefined) decimals = 2;
93
+ if (Number.isInteger(value)) return String(value);
94
+ var s = value.toFixed(decimals);
95
+ // Remove trailing zeros after decimal point
96
+ s = s.replace(/\.?0+$/, '');
97
+ return s;
98
+ }
99
+
100
+ /**
101
+ * Auto-bin an array of numbers into `binCount` histogram bins.
102
+ * Returns an array of { min, max, count } objects.
103
+ * @param {number[]} values
104
+ * @param {number} binCount
105
+ * @returns {Array<{min: number, max: number, count: number}>}
106
+ */
107
+ function autoBin(values, binCount) {
108
+ if (!values || values.length === 0) return [];
109
+ var minVal = min(values);
110
+ var maxVal = max(values);
111
+ if (minVal === maxVal) {
112
+ return [{ min: minVal, max: maxVal, count: values.length }];
113
+ }
114
+ var binWidth = (maxVal - minVal) / binCount;
115
+ var bins = [];
116
+ for (var i = 0; i < binCount; i++) {
117
+ bins.push({
118
+ min: minVal + i * binWidth,
119
+ max: minVal + (i + 1) * binWidth,
120
+ count: 0
121
+ });
122
+ }
123
+ for (var j = 0; j < values.length; j++) {
124
+ var v = values[j];
125
+ var idx = Math.floor((v - minVal) / binWidth);
126
+ // Clamp last value into last bin
127
+ if (idx >= binCount) idx = binCount - 1;
128
+ bins[idx].count++;
129
+ }
130
+ return bins;
131
+ }
132
+
133
+ /**
134
+ * Pad a string on the left to a given width.
135
+ * @param {string} s
136
+ * @param {number} width
137
+ * @param {string} [char=' ']
138
+ * @returns {string}
139
+ */
140
+ function padLeft(s, width, char) {
141
+ if (char === undefined) char = ' ';
142
+ s = String(s);
143
+ while (s.length < width) s = char + s;
144
+ return s;
145
+ }
146
+
147
+ /**
148
+ * Pad a string on the right to a given width.
149
+ * @param {string} s
150
+ * @param {number} width
151
+ * @param {string} [char=' ']
152
+ * @returns {string}
153
+ */
154
+ function padRight(s, width, char) {
155
+ if (char === undefined) char = ' ';
156
+ s = String(s);
157
+ while (s.length < width) s = s + char;
158
+ return s;
159
+ }
160
+
161
+ /**
162
+ * Truncate a string to maxLen, appending '…' if truncated.
163
+ * @param {string} s
164
+ * @param {number} maxLen
165
+ * @returns {string}
166
+ */
167
+ function truncate(s, maxLen) {
168
+ s = String(s);
169
+ if (s.length <= maxLen) return s;
170
+ return s.slice(0, maxLen - 1) + '\u2026';
171
+ }
172
+
173
+ module.exports = {
174
+ min: min,
175
+ max: max,
176
+ normalize: normalize,
177
+ scaleToRange: scaleToRange,
178
+ mapToRange: mapToRange,
179
+ niceStep: niceStep,
180
+ formatNum: formatNum,
181
+ autoBin: autoBin,
182
+ padLeft: padLeft,
183
+ padRight: padRight,
184
+ truncate: truncate
185
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "pipechart",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency CLI tool that pipes JSON to real-time ASCII charts in the terminal",
5
+ "main": "bin/pipechart.js",
6
+ "bin": {
7
+ "pipechart": "./bin/pipechart.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/termviz.js"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "chart",
15
+ "ascii",
16
+ "terminal",
17
+ "json",
18
+ "visualization",
19
+ "sparkline",
20
+ "histogram",
21
+ "bar-chart",
22
+ "line-chart",
23
+ "ansi"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "dependencies": {},
28
+ "engines": {
29
+ "node": ">=12"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/yourusername/pipechart.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/yourusername/pipechart/issues"
37
+ },
38
+ "homepage": "https://github.com/yourusername/pipechart#readme"
39
+ }