@tuicomponents/sparkline 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/dist/index.cjs +185 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +129 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
HEIGHT_BLOCKS: () => HEIGHT_BLOCKS,
|
|
24
|
+
SparklineComponent: () => SparklineComponent,
|
|
25
|
+
computeSparklineLayout: () => computeSparklineLayout,
|
|
26
|
+
createSparkline: () => createSparkline,
|
|
27
|
+
renderSparklineAnsi: () => renderSparklineAnsi,
|
|
28
|
+
renderSparklineMarkdown: () => renderSparklineMarkdown,
|
|
29
|
+
sparklineInputSchema: () => sparklineInputSchema
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/sparkline.ts
|
|
34
|
+
var import_core2 = require("@tuicomponents/core");
|
|
35
|
+
|
|
36
|
+
// src/schema.ts
|
|
37
|
+
var import_zod = require("zod");
|
|
38
|
+
var sparklineInputSchema = import_zod.z.object({
|
|
39
|
+
/** Array of numeric data points to visualize (at least 1 required) */
|
|
40
|
+
values: import_zod.z.array(import_zod.z.number()).min(1),
|
|
41
|
+
/** Compress output to this width if less than values.length */
|
|
42
|
+
width: import_zod.z.number().int().positive().optional(),
|
|
43
|
+
/** Explicit minimum value for scaling (defaults to min of values) */
|
|
44
|
+
min: import_zod.z.number().optional(),
|
|
45
|
+
/** Explicit maximum value for scaling (defaults to max of values) */
|
|
46
|
+
max: import_zod.z.number().optional(),
|
|
47
|
+
/** Optional label prefix (e.g., "CPU: ") */
|
|
48
|
+
label: import_zod.z.string().optional()
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// src/layout.ts
|
|
52
|
+
var HEIGHT_BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
53
|
+
var MIDDLE_BLOCK_INDEX = 4;
|
|
54
|
+
function valueToBlockIndex(value, min, max) {
|
|
55
|
+
if (max === min) {
|
|
56
|
+
return MIDDLE_BLOCK_INDEX;
|
|
57
|
+
}
|
|
58
|
+
const normalized = (value - min) / (max - min);
|
|
59
|
+
const index = Math.floor(normalized * 8);
|
|
60
|
+
return Math.max(0, Math.min(7, index));
|
|
61
|
+
}
|
|
62
|
+
function bucketValues(values, targetWidth) {
|
|
63
|
+
if (targetWidth >= values.length) {
|
|
64
|
+
return values;
|
|
65
|
+
}
|
|
66
|
+
const result = [];
|
|
67
|
+
const bucketSize = values.length / targetWidth;
|
|
68
|
+
for (let i = 0; i < targetWidth; i++) {
|
|
69
|
+
const startIndex = Math.floor(i * bucketSize);
|
|
70
|
+
const endIndex = Math.floor((i + 1) * bucketSize);
|
|
71
|
+
let sum = 0;
|
|
72
|
+
let count = 0;
|
|
73
|
+
for (let j = startIndex; j < endIndex && j < values.length; j++) {
|
|
74
|
+
const value = values[j];
|
|
75
|
+
if (value !== void 0) {
|
|
76
|
+
sum += value;
|
|
77
|
+
count++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
result.push(count > 0 ? sum / count : 0);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
function computeSparklineLayout(input) {
|
|
85
|
+
const { values, width, min: explicitMin, max: explicitMax, label } = input;
|
|
86
|
+
const displayValues = width !== void 0 && width < values.length ? bucketValues(values, width) : values;
|
|
87
|
+
const actualMin = Math.min(...displayValues);
|
|
88
|
+
const actualMax = Math.max(...displayValues);
|
|
89
|
+
const min = explicitMin ?? actualMin;
|
|
90
|
+
const max = explicitMax ?? actualMax;
|
|
91
|
+
const blocks = displayValues.map((v) => {
|
|
92
|
+
const clampedValue = Math.max(min, Math.min(max, v));
|
|
93
|
+
const index = valueToBlockIndex(clampedValue, min, max);
|
|
94
|
+
return HEIGHT_BLOCKS[index];
|
|
95
|
+
}).join("");
|
|
96
|
+
const labelWidth = label?.length ?? 0;
|
|
97
|
+
const blocksWidth = displayValues.length;
|
|
98
|
+
const totalWidth = labelWidth + blocksWidth;
|
|
99
|
+
return {
|
|
100
|
+
blocks,
|
|
101
|
+
label,
|
|
102
|
+
totalWidth
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/renderers.ts
|
|
107
|
+
var import_core = require("@tuicomponents/core");
|
|
108
|
+
function renderSparklineAnsi(layout, theme) {
|
|
109
|
+
const { blocks, label } = layout;
|
|
110
|
+
const styledBlocks = theme ? theme.semantic.primary(blocks) : blocks;
|
|
111
|
+
const styledLabel = label ? theme ? theme.semantic.header(label) : label : "";
|
|
112
|
+
return styledLabel + styledBlocks;
|
|
113
|
+
}
|
|
114
|
+
function renderSparklineMarkdown(layout, context) {
|
|
115
|
+
const { blocks, label } = layout;
|
|
116
|
+
const styledBlocks = context.style.secondary(blocks);
|
|
117
|
+
const styledLabel = label ?? "";
|
|
118
|
+
return (0, import_core.anchorLine)(styledLabel + styledBlocks, import_core.DEFAULT_ANCHOR);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/sparkline.ts
|
|
122
|
+
var SparklineComponent = class extends import_core2.BaseTuiComponent {
|
|
123
|
+
metadata = {
|
|
124
|
+
name: "sparkline",
|
|
125
|
+
description: "Compact inline sparkline visualization using height block characters (\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588)",
|
|
126
|
+
version: "0.1.0",
|
|
127
|
+
supportedModes: ["ansi", "markdown"],
|
|
128
|
+
examples: [
|
|
129
|
+
{
|
|
130
|
+
name: "basic",
|
|
131
|
+
description: "Simple ascending values",
|
|
132
|
+
input: { values: [1, 2, 3, 4, 5, 6, 7, 8] }
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "with-label",
|
|
136
|
+
description: "Sparkline with a label prefix",
|
|
137
|
+
input: { values: [10, 25, 40, 35, 50, 45, 60], label: "Revenue: " }
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "compressed",
|
|
141
|
+
description: "Data compressed to fewer columns",
|
|
142
|
+
input: {
|
|
143
|
+
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
|
144
|
+
width: 6
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "explicit-range",
|
|
149
|
+
description: "Values scaled to explicit min/max range",
|
|
150
|
+
input: {
|
|
151
|
+
values: [50, 60, 70, 65, 75],
|
|
152
|
+
min: 0,
|
|
153
|
+
max: 100
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
};
|
|
158
|
+
schema = sparklineInputSchema;
|
|
159
|
+
render(input, context) {
|
|
160
|
+
const parsed = this.schema.parse(input);
|
|
161
|
+
const layout = computeSparklineLayout(parsed);
|
|
162
|
+
const output = context.renderMode === "markdown" ? renderSparklineMarkdown(layout, context) : renderSparklineAnsi(layout, context.theme);
|
|
163
|
+
const measured = (0, import_core2.measureLines)(output);
|
|
164
|
+
return {
|
|
165
|
+
output,
|
|
166
|
+
actualWidth: measured.maxWidth,
|
|
167
|
+
lineCount: measured.lineCount
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function createSparkline() {
|
|
172
|
+
return new SparklineComponent();
|
|
173
|
+
}
|
|
174
|
+
import_core2.registry.register(createSparkline);
|
|
175
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
176
|
+
0 && (module.exports = {
|
|
177
|
+
HEIGHT_BLOCKS,
|
|
178
|
+
SparklineComponent,
|
|
179
|
+
computeSparklineLayout,
|
|
180
|
+
createSparkline,
|
|
181
|
+
renderSparklineAnsi,
|
|
182
|
+
renderSparklineMarkdown,
|
|
183
|
+
sparklineInputSchema
|
|
184
|
+
});
|
|
185
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sparkline.ts","../src/schema.ts","../src/layout.ts","../src/renderers.ts"],"sourcesContent":["// Component exports\nexport { createSparkline, SparklineComponent } from \"./sparkline.js\";\n\n// Schema exports\nexport {\n sparklineInputSchema,\n type SparklineInput,\n type SparklineInputWithDefaults,\n} from \"./schema.js\";\n\n// Layout exports (for advanced usage)\nexport {\n HEIGHT_BLOCKS,\n computeSparklineLayout,\n type SparklineLayout,\n} from \"./layout.js\";\n\n// Renderer exports (for advanced usage)\nexport { renderSparklineAnsi, renderSparklineMarkdown } from \"./renderers.js\";\n","import {\n BaseTuiComponent,\n measureLines,\n registry,\n type ComponentMetadata,\n type RenderContext,\n type RenderResult,\n} from \"@tuicomponents/core\";\nimport { sparklineInputSchema, type SparklineInput } from \"./schema.js\";\nimport { computeSparklineLayout } from \"./layout.js\";\nimport { renderSparklineAnsi, renderSparklineMarkdown } from \"./renderers.js\";\n\n/**\n * Sparkline component for compact inline data visualization.\n *\n * Renders numeric data as a row of height block characters: ▁▂▃▄▅▆▇█\n *\n * @example\n * ```ts\n * const sparkline = createSparkline();\n * const result = sparkline.render(\n * { values: [1, 3, 5, 7, 6, 4, 2], label: \"Trend: \" },\n * context\n * );\n * // Output: \"Trend: ▁▃▅▇▆▄▂\"\n * ```\n */\nexport class SparklineComponent extends BaseTuiComponent<\n SparklineInput,\n typeof sparklineInputSchema\n> {\n readonly metadata: ComponentMetadata<SparklineInput> = {\n name: \"sparkline\",\n description:\n \"Compact inline sparkline visualization using height block characters (▁▂▃▄▅▆▇█)\",\n version: \"0.1.0\",\n supportedModes: [\"ansi\", \"markdown\"],\n examples: [\n {\n name: \"basic\",\n description: \"Simple ascending values\",\n input: { values: [1, 2, 3, 4, 5, 6, 7, 8] },\n },\n {\n name: \"with-label\",\n description: \"Sparkline with a label prefix\",\n input: { values: [10, 25, 40, 35, 50, 45, 60], label: \"Revenue: \" },\n },\n {\n name: \"compressed\",\n description: \"Data compressed to fewer columns\",\n input: {\n values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n width: 6,\n },\n },\n {\n name: \"explicit-range\",\n description: \"Values scaled to explicit min/max range\",\n input: {\n values: [50, 60, 70, 65, 75],\n min: 0,\n max: 100,\n },\n },\n ],\n };\n\n readonly schema = sparklineInputSchema;\n\n render(input: SparklineInput, context: RenderContext): RenderResult {\n // Parse input (applies defaults)\n const parsed = this.schema.parse(input);\n\n // Compute layout\n const layout = computeSparklineLayout(parsed);\n\n // Render based on mode\n const output =\n context.renderMode === \"markdown\"\n ? renderSparklineMarkdown(layout, context)\n : renderSparklineAnsi(layout, context.theme);\n\n // Measure output\n const measured = measureLines(output);\n\n return {\n output,\n actualWidth: measured.maxWidth,\n lineCount: measured.lineCount,\n };\n }\n}\n\n/**\n * Factory function to create a SparklineComponent instance.\n */\nexport function createSparkline(): SparklineComponent {\n return new SparklineComponent();\n}\n\n// Auto-register with global registry\nregistry.register(createSparkline);\n","import { z } from \"zod\";\n\n/**\n * Input schema for the sparkline component.\n */\nexport const sparklineInputSchema = z.object({\n /** Array of numeric data points to visualize (at least 1 required) */\n values: z.array(z.number()).min(1),\n /** Compress output to this width if less than values.length */\n width: z.number().int().positive().optional(),\n /** Explicit minimum value for scaling (defaults to min of values) */\n min: z.number().optional(),\n /** Explicit maximum value for scaling (defaults to max of values) */\n max: z.number().optional(),\n /** Optional label prefix (e.g., \"CPU: \") */\n label: z.string().optional(),\n});\n\n/**\n * Input type for the sparkline component (before defaults applied).\n */\nexport type SparklineInput = z.input<typeof sparklineInputSchema>;\n\n/**\n * Input type for the sparkline component (after defaults applied).\n */\nexport type SparklineInputWithDefaults = z.output<typeof sparklineInputSchema>;\n","import type { SparklineInputWithDefaults } from \"./schema.js\";\n\n/**\n * Height block characters from lowest to highest.\n * Index 0 is the shortest bar, index 7 is the tallest.\n */\nexport const HEIGHT_BLOCKS = [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\"] as const;\n\n/**\n * Middle block index (used when all values are equal).\n */\nconst MIDDLE_BLOCK_INDEX = 4;\n\n/**\n * Layout information for a sparkline.\n */\nexport interface SparklineLayout {\n /** The computed sparkline string (blocks only) */\n blocks: string;\n /** Optional label prefix */\n label: string | undefined;\n /** Total visual width of the output */\n totalWidth: number;\n}\n\n/**\n * Map a value to a height block index (0-7).\n *\n * @param value - The value to map\n * @param min - Minimum value in the range\n * @param max - Maximum value in the range\n * @returns Block index (0-7)\n */\nfunction valueToBlockIndex(value: number, min: number, max: number): number {\n // If min === max, all values are equal, use middle block\n if (max === min) {\n return MIDDLE_BLOCK_INDEX;\n }\n\n // Normalize value to 0-1 range\n const normalized = (value - min) / (max - min);\n\n // Scale to 0-7 range and clamp\n // We use Math.floor and then clamp to ensure we get 0-7\n const index = Math.floor(normalized * 8);\n\n // Clamp to valid range (handles edge cases like value === max)\n return Math.max(0, Math.min(7, index));\n}\n\n/**\n * Bucket values into groups and return averaged values.\n *\n * @param values - Array of values to bucket\n * @param targetWidth - Target number of buckets\n * @returns Array of averaged values\n */\nfunction bucketValues(values: number[], targetWidth: number): number[] {\n if (targetWidth >= values.length) {\n return values;\n }\n\n const result: number[] = [];\n const bucketSize = values.length / targetWidth;\n\n for (let i = 0; i < targetWidth; i++) {\n const startIndex = Math.floor(i * bucketSize);\n const endIndex = Math.floor((i + 1) * bucketSize);\n\n // Calculate average for this bucket\n let sum = 0;\n let count = 0;\n for (let j = startIndex; j < endIndex && j < values.length; j++) {\n const value = values[j];\n if (value !== undefined) {\n sum += value;\n count++;\n }\n }\n\n result.push(count > 0 ? sum / count : 0);\n }\n\n return result;\n}\n\n/**\n * Compute the layout for a sparkline.\n *\n * @param input - Validated sparkline input\n * @returns Layout information\n */\nexport function computeSparklineLayout(\n input: SparklineInputWithDefaults\n): SparklineLayout {\n const { values, width, min: explicitMin, max: explicitMax, label } = input;\n\n // Apply width compression if needed\n const displayValues =\n width !== undefined && width < values.length\n ? bucketValues(values, width)\n : values;\n\n // Determine min/max for scaling\n const actualMin = Math.min(...displayValues);\n const actualMax = Math.max(...displayValues);\n const min = explicitMin ?? actualMin;\n const max = explicitMax ?? actualMax;\n\n // Convert values to block characters\n const blocks = displayValues\n .map((v) => {\n // Clamp value to explicit min/max range if provided\n const clampedValue = Math.max(min, Math.min(max, v));\n const index = valueToBlockIndex(clampedValue, min, max);\n return HEIGHT_BLOCKS[index];\n })\n .join(\"\");\n\n // Calculate total width\n const labelWidth = label?.length ?? 0;\n const blocksWidth = displayValues.length;\n const totalWidth = labelWidth + blocksWidth;\n\n return {\n blocks,\n label,\n totalWidth,\n };\n}\n","import {\n anchorLine,\n DEFAULT_ANCHOR,\n type RenderContext,\n} from \"@tuicomponents/core\";\nimport type { TuiTheme } from \"@tuicomponents/core\";\nimport type { SparklineLayout } from \"./layout.js\";\n\n/**\n * Render a sparkline in ANSI mode.\n *\n * @param layout - Computed sparkline layout\n * @param theme - Optional theme for styling\n * @returns Rendered ANSI string\n */\nexport function renderSparklineAnsi(\n layout: SparklineLayout,\n theme?: TuiTheme\n): string {\n const { blocks, label } = layout;\n\n // Apply theme if available\n const styledBlocks = theme ? theme.semantic.primary(blocks) : blocks;\n const styledLabel = label\n ? theme\n ? theme.semantic.header(label)\n : label\n : \"\";\n\n return styledLabel + styledBlocks;\n}\n\n/**\n * Render a sparkline in markdown mode.\n *\n * Uses context.style.secondary() to apply semantic styling to the blocks,\n * which wraps them in backticks for visual distinction in markdown renderers.\n * The anchor character is prepended to preserve leading whitespace.\n *\n * @param layout - Computed sparkline layout\n * @param context - Render context for styling\n * @returns Rendered markdown string\n */\nexport function renderSparklineMarkdown(\n layout: SparklineLayout,\n context: RenderContext\n): string {\n const { blocks, label } = layout;\n\n // Apply secondary styling to the data blocks\n // In markdown mode, this wraps in backticks for visual distinction\n const styledBlocks = context.style.secondary(blocks);\n const styledLabel = label ?? \"\";\n\n // Add anchor to preserve leading whitespace in markdown\n return anchorLine(styledLabel + styledBlocks, DEFAULT_ANCHOR);\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as zod from 'zod';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseTuiComponent, ComponentMetadata, RenderContext, RenderResult, TuiTheme } from '@tuicomponents/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Input schema for the sparkline component.
|
|
7
|
+
*/
|
|
8
|
+
declare const sparklineInputSchema: z.ZodObject<{
|
|
9
|
+
/** Array of numeric data points to visualize (at least 1 required) */
|
|
10
|
+
values: z.ZodArray<z.ZodNumber, "many">;
|
|
11
|
+
/** Compress output to this width if less than values.length */
|
|
12
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
/** Explicit minimum value for scaling (defaults to min of values) */
|
|
14
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
/** Explicit maximum value for scaling (defaults to max of values) */
|
|
16
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
/** Optional label prefix (e.g., "CPU: ") */
|
|
18
|
+
label: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
values: number[];
|
|
21
|
+
width?: number | undefined;
|
|
22
|
+
min?: number | undefined;
|
|
23
|
+
max?: number | undefined;
|
|
24
|
+
label?: string | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
values: number[];
|
|
27
|
+
width?: number | undefined;
|
|
28
|
+
min?: number | undefined;
|
|
29
|
+
max?: number | undefined;
|
|
30
|
+
label?: string | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Input type for the sparkline component (before defaults applied).
|
|
34
|
+
*/
|
|
35
|
+
type SparklineInput = z.input<typeof sparklineInputSchema>;
|
|
36
|
+
/**
|
|
37
|
+
* Input type for the sparkline component (after defaults applied).
|
|
38
|
+
*/
|
|
39
|
+
type SparklineInputWithDefaults = z.output<typeof sparklineInputSchema>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sparkline component for compact inline data visualization.
|
|
43
|
+
*
|
|
44
|
+
* Renders numeric data as a row of height block characters: ▁▂▃▄▅▆▇█
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const sparkline = createSparkline();
|
|
49
|
+
* const result = sparkline.render(
|
|
50
|
+
* { values: [1, 3, 5, 7, 6, 4, 2], label: "Trend: " },
|
|
51
|
+
* context
|
|
52
|
+
* );
|
|
53
|
+
* // Output: "Trend: ▁▃▅▇▆▄▂"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare class SparklineComponent extends BaseTuiComponent<SparklineInput, typeof sparklineInputSchema> {
|
|
57
|
+
readonly metadata: ComponentMetadata<SparklineInput>;
|
|
58
|
+
readonly schema: zod.ZodObject<{
|
|
59
|
+
values: zod.ZodArray<zod.ZodNumber, "many">;
|
|
60
|
+
width: zod.ZodOptional<zod.ZodNumber>;
|
|
61
|
+
min: zod.ZodOptional<zod.ZodNumber>;
|
|
62
|
+
max: zod.ZodOptional<zod.ZodNumber>;
|
|
63
|
+
label: zod.ZodOptional<zod.ZodString>;
|
|
64
|
+
}, "strip", zod.ZodTypeAny, {
|
|
65
|
+
values: number[];
|
|
66
|
+
width?: number | undefined;
|
|
67
|
+
min?: number | undefined;
|
|
68
|
+
max?: number | undefined;
|
|
69
|
+
label?: string | undefined;
|
|
70
|
+
}, {
|
|
71
|
+
values: number[];
|
|
72
|
+
width?: number | undefined;
|
|
73
|
+
min?: number | undefined;
|
|
74
|
+
max?: number | undefined;
|
|
75
|
+
label?: string | undefined;
|
|
76
|
+
}>;
|
|
77
|
+
render(input: SparklineInput, context: RenderContext): RenderResult;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Factory function to create a SparklineComponent instance.
|
|
81
|
+
*/
|
|
82
|
+
declare function createSparkline(): SparklineComponent;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Height block characters from lowest to highest.
|
|
86
|
+
* Index 0 is the shortest bar, index 7 is the tallest.
|
|
87
|
+
*/
|
|
88
|
+
declare const HEIGHT_BLOCKS: readonly ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
89
|
+
/**
|
|
90
|
+
* Layout information for a sparkline.
|
|
91
|
+
*/
|
|
92
|
+
interface SparklineLayout {
|
|
93
|
+
/** The computed sparkline string (blocks only) */
|
|
94
|
+
blocks: string;
|
|
95
|
+
/** Optional label prefix */
|
|
96
|
+
label: string | undefined;
|
|
97
|
+
/** Total visual width of the output */
|
|
98
|
+
totalWidth: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Compute the layout for a sparkline.
|
|
102
|
+
*
|
|
103
|
+
* @param input - Validated sparkline input
|
|
104
|
+
* @returns Layout information
|
|
105
|
+
*/
|
|
106
|
+
declare function computeSparklineLayout(input: SparklineInputWithDefaults): SparklineLayout;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Render a sparkline in ANSI mode.
|
|
110
|
+
*
|
|
111
|
+
* @param layout - Computed sparkline layout
|
|
112
|
+
* @param theme - Optional theme for styling
|
|
113
|
+
* @returns Rendered ANSI string
|
|
114
|
+
*/
|
|
115
|
+
declare function renderSparklineAnsi(layout: SparklineLayout, theme?: TuiTheme): string;
|
|
116
|
+
/**
|
|
117
|
+
* Render a sparkline in markdown mode.
|
|
118
|
+
*
|
|
119
|
+
* Uses context.style.secondary() to apply semantic styling to the blocks,
|
|
120
|
+
* which wraps them in backticks for visual distinction in markdown renderers.
|
|
121
|
+
* The anchor character is prepended to preserve leading whitespace.
|
|
122
|
+
*
|
|
123
|
+
* @param layout - Computed sparkline layout
|
|
124
|
+
* @param context - Render context for styling
|
|
125
|
+
* @returns Rendered markdown string
|
|
126
|
+
*/
|
|
127
|
+
declare function renderSparklineMarkdown(layout: SparklineLayout, context: RenderContext): string;
|
|
128
|
+
|
|
129
|
+
export { HEIGHT_BLOCKS, SparklineComponent, type SparklineInput, type SparklineInputWithDefaults, type SparklineLayout, computeSparklineLayout, createSparkline, renderSparklineAnsi, renderSparklineMarkdown, sparklineInputSchema };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as zod from 'zod';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseTuiComponent, ComponentMetadata, RenderContext, RenderResult, TuiTheme } from '@tuicomponents/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Input schema for the sparkline component.
|
|
7
|
+
*/
|
|
8
|
+
declare const sparklineInputSchema: z.ZodObject<{
|
|
9
|
+
/** Array of numeric data points to visualize (at least 1 required) */
|
|
10
|
+
values: z.ZodArray<z.ZodNumber, "many">;
|
|
11
|
+
/** Compress output to this width if less than values.length */
|
|
12
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
/** Explicit minimum value for scaling (defaults to min of values) */
|
|
14
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
/** Explicit maximum value for scaling (defaults to max of values) */
|
|
16
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
/** Optional label prefix (e.g., "CPU: ") */
|
|
18
|
+
label: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
values: number[];
|
|
21
|
+
width?: number | undefined;
|
|
22
|
+
min?: number | undefined;
|
|
23
|
+
max?: number | undefined;
|
|
24
|
+
label?: string | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
values: number[];
|
|
27
|
+
width?: number | undefined;
|
|
28
|
+
min?: number | undefined;
|
|
29
|
+
max?: number | undefined;
|
|
30
|
+
label?: string | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Input type for the sparkline component (before defaults applied).
|
|
34
|
+
*/
|
|
35
|
+
type SparklineInput = z.input<typeof sparklineInputSchema>;
|
|
36
|
+
/**
|
|
37
|
+
* Input type for the sparkline component (after defaults applied).
|
|
38
|
+
*/
|
|
39
|
+
type SparklineInputWithDefaults = z.output<typeof sparklineInputSchema>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sparkline component for compact inline data visualization.
|
|
43
|
+
*
|
|
44
|
+
* Renders numeric data as a row of height block characters: ▁▂▃▄▅▆▇█
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const sparkline = createSparkline();
|
|
49
|
+
* const result = sparkline.render(
|
|
50
|
+
* { values: [1, 3, 5, 7, 6, 4, 2], label: "Trend: " },
|
|
51
|
+
* context
|
|
52
|
+
* );
|
|
53
|
+
* // Output: "Trend: ▁▃▅▇▆▄▂"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare class SparklineComponent extends BaseTuiComponent<SparklineInput, typeof sparklineInputSchema> {
|
|
57
|
+
readonly metadata: ComponentMetadata<SparklineInput>;
|
|
58
|
+
readonly schema: zod.ZodObject<{
|
|
59
|
+
values: zod.ZodArray<zod.ZodNumber, "many">;
|
|
60
|
+
width: zod.ZodOptional<zod.ZodNumber>;
|
|
61
|
+
min: zod.ZodOptional<zod.ZodNumber>;
|
|
62
|
+
max: zod.ZodOptional<zod.ZodNumber>;
|
|
63
|
+
label: zod.ZodOptional<zod.ZodString>;
|
|
64
|
+
}, "strip", zod.ZodTypeAny, {
|
|
65
|
+
values: number[];
|
|
66
|
+
width?: number | undefined;
|
|
67
|
+
min?: number | undefined;
|
|
68
|
+
max?: number | undefined;
|
|
69
|
+
label?: string | undefined;
|
|
70
|
+
}, {
|
|
71
|
+
values: number[];
|
|
72
|
+
width?: number | undefined;
|
|
73
|
+
min?: number | undefined;
|
|
74
|
+
max?: number | undefined;
|
|
75
|
+
label?: string | undefined;
|
|
76
|
+
}>;
|
|
77
|
+
render(input: SparklineInput, context: RenderContext): RenderResult;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Factory function to create a SparklineComponent instance.
|
|
81
|
+
*/
|
|
82
|
+
declare function createSparkline(): SparklineComponent;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Height block characters from lowest to highest.
|
|
86
|
+
* Index 0 is the shortest bar, index 7 is the tallest.
|
|
87
|
+
*/
|
|
88
|
+
declare const HEIGHT_BLOCKS: readonly ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
89
|
+
/**
|
|
90
|
+
* Layout information for a sparkline.
|
|
91
|
+
*/
|
|
92
|
+
interface SparklineLayout {
|
|
93
|
+
/** The computed sparkline string (blocks only) */
|
|
94
|
+
blocks: string;
|
|
95
|
+
/** Optional label prefix */
|
|
96
|
+
label: string | undefined;
|
|
97
|
+
/** Total visual width of the output */
|
|
98
|
+
totalWidth: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Compute the layout for a sparkline.
|
|
102
|
+
*
|
|
103
|
+
* @param input - Validated sparkline input
|
|
104
|
+
* @returns Layout information
|
|
105
|
+
*/
|
|
106
|
+
declare function computeSparklineLayout(input: SparklineInputWithDefaults): SparklineLayout;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Render a sparkline in ANSI mode.
|
|
110
|
+
*
|
|
111
|
+
* @param layout - Computed sparkline layout
|
|
112
|
+
* @param theme - Optional theme for styling
|
|
113
|
+
* @returns Rendered ANSI string
|
|
114
|
+
*/
|
|
115
|
+
declare function renderSparklineAnsi(layout: SparklineLayout, theme?: TuiTheme): string;
|
|
116
|
+
/**
|
|
117
|
+
* Render a sparkline in markdown mode.
|
|
118
|
+
*
|
|
119
|
+
* Uses context.style.secondary() to apply semantic styling to the blocks,
|
|
120
|
+
* which wraps them in backticks for visual distinction in markdown renderers.
|
|
121
|
+
* The anchor character is prepended to preserve leading whitespace.
|
|
122
|
+
*
|
|
123
|
+
* @param layout - Computed sparkline layout
|
|
124
|
+
* @param context - Render context for styling
|
|
125
|
+
* @returns Rendered markdown string
|
|
126
|
+
*/
|
|
127
|
+
declare function renderSparklineMarkdown(layout: SparklineLayout, context: RenderContext): string;
|
|
128
|
+
|
|
129
|
+
export { HEIGHT_BLOCKS, SparklineComponent, type SparklineInput, type SparklineInputWithDefaults, type SparklineLayout, computeSparklineLayout, createSparkline, renderSparklineAnsi, renderSparklineMarkdown, sparklineInputSchema };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// src/sparkline.ts
|
|
2
|
+
import {
|
|
3
|
+
BaseTuiComponent,
|
|
4
|
+
measureLines,
|
|
5
|
+
registry
|
|
6
|
+
} from "@tuicomponents/core";
|
|
7
|
+
|
|
8
|
+
// src/schema.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
var sparklineInputSchema = z.object({
|
|
11
|
+
/** Array of numeric data points to visualize (at least 1 required) */
|
|
12
|
+
values: z.array(z.number()).min(1),
|
|
13
|
+
/** Compress output to this width if less than values.length */
|
|
14
|
+
width: z.number().int().positive().optional(),
|
|
15
|
+
/** Explicit minimum value for scaling (defaults to min of values) */
|
|
16
|
+
min: z.number().optional(),
|
|
17
|
+
/** Explicit maximum value for scaling (defaults to max of values) */
|
|
18
|
+
max: z.number().optional(),
|
|
19
|
+
/** Optional label prefix (e.g., "CPU: ") */
|
|
20
|
+
label: z.string().optional()
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// src/layout.ts
|
|
24
|
+
var HEIGHT_BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
25
|
+
var MIDDLE_BLOCK_INDEX = 4;
|
|
26
|
+
function valueToBlockIndex(value, min, max) {
|
|
27
|
+
if (max === min) {
|
|
28
|
+
return MIDDLE_BLOCK_INDEX;
|
|
29
|
+
}
|
|
30
|
+
const normalized = (value - min) / (max - min);
|
|
31
|
+
const index = Math.floor(normalized * 8);
|
|
32
|
+
return Math.max(0, Math.min(7, index));
|
|
33
|
+
}
|
|
34
|
+
function bucketValues(values, targetWidth) {
|
|
35
|
+
if (targetWidth >= values.length) {
|
|
36
|
+
return values;
|
|
37
|
+
}
|
|
38
|
+
const result = [];
|
|
39
|
+
const bucketSize = values.length / targetWidth;
|
|
40
|
+
for (let i = 0; i < targetWidth; i++) {
|
|
41
|
+
const startIndex = Math.floor(i * bucketSize);
|
|
42
|
+
const endIndex = Math.floor((i + 1) * bucketSize);
|
|
43
|
+
let sum = 0;
|
|
44
|
+
let count = 0;
|
|
45
|
+
for (let j = startIndex; j < endIndex && j < values.length; j++) {
|
|
46
|
+
const value = values[j];
|
|
47
|
+
if (value !== void 0) {
|
|
48
|
+
sum += value;
|
|
49
|
+
count++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
result.push(count > 0 ? sum / count : 0);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function computeSparklineLayout(input) {
|
|
57
|
+
const { values, width, min: explicitMin, max: explicitMax, label } = input;
|
|
58
|
+
const displayValues = width !== void 0 && width < values.length ? bucketValues(values, width) : values;
|
|
59
|
+
const actualMin = Math.min(...displayValues);
|
|
60
|
+
const actualMax = Math.max(...displayValues);
|
|
61
|
+
const min = explicitMin ?? actualMin;
|
|
62
|
+
const max = explicitMax ?? actualMax;
|
|
63
|
+
const blocks = displayValues.map((v) => {
|
|
64
|
+
const clampedValue = Math.max(min, Math.min(max, v));
|
|
65
|
+
const index = valueToBlockIndex(clampedValue, min, max);
|
|
66
|
+
return HEIGHT_BLOCKS[index];
|
|
67
|
+
}).join("");
|
|
68
|
+
const labelWidth = label?.length ?? 0;
|
|
69
|
+
const blocksWidth = displayValues.length;
|
|
70
|
+
const totalWidth = labelWidth + blocksWidth;
|
|
71
|
+
return {
|
|
72
|
+
blocks,
|
|
73
|
+
label,
|
|
74
|
+
totalWidth
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/renderers.ts
|
|
79
|
+
import {
|
|
80
|
+
anchorLine,
|
|
81
|
+
DEFAULT_ANCHOR
|
|
82
|
+
} from "@tuicomponents/core";
|
|
83
|
+
function renderSparklineAnsi(layout, theme) {
|
|
84
|
+
const { blocks, label } = layout;
|
|
85
|
+
const styledBlocks = theme ? theme.semantic.primary(blocks) : blocks;
|
|
86
|
+
const styledLabel = label ? theme ? theme.semantic.header(label) : label : "";
|
|
87
|
+
return styledLabel + styledBlocks;
|
|
88
|
+
}
|
|
89
|
+
function renderSparklineMarkdown(layout, context) {
|
|
90
|
+
const { blocks, label } = layout;
|
|
91
|
+
const styledBlocks = context.style.secondary(blocks);
|
|
92
|
+
const styledLabel = label ?? "";
|
|
93
|
+
return anchorLine(styledLabel + styledBlocks, DEFAULT_ANCHOR);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/sparkline.ts
|
|
97
|
+
var SparklineComponent = class extends BaseTuiComponent {
|
|
98
|
+
metadata = {
|
|
99
|
+
name: "sparkline",
|
|
100
|
+
description: "Compact inline sparkline visualization using height block characters (\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588)",
|
|
101
|
+
version: "0.1.0",
|
|
102
|
+
supportedModes: ["ansi", "markdown"],
|
|
103
|
+
examples: [
|
|
104
|
+
{
|
|
105
|
+
name: "basic",
|
|
106
|
+
description: "Simple ascending values",
|
|
107
|
+
input: { values: [1, 2, 3, 4, 5, 6, 7, 8] }
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "with-label",
|
|
111
|
+
description: "Sparkline with a label prefix",
|
|
112
|
+
input: { values: [10, 25, 40, 35, 50, 45, 60], label: "Revenue: " }
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "compressed",
|
|
116
|
+
description: "Data compressed to fewer columns",
|
|
117
|
+
input: {
|
|
118
|
+
values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
|
119
|
+
width: 6
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "explicit-range",
|
|
124
|
+
description: "Values scaled to explicit min/max range",
|
|
125
|
+
input: {
|
|
126
|
+
values: [50, 60, 70, 65, 75],
|
|
127
|
+
min: 0,
|
|
128
|
+
max: 100
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
};
|
|
133
|
+
schema = sparklineInputSchema;
|
|
134
|
+
render(input, context) {
|
|
135
|
+
const parsed = this.schema.parse(input);
|
|
136
|
+
const layout = computeSparklineLayout(parsed);
|
|
137
|
+
const output = context.renderMode === "markdown" ? renderSparklineMarkdown(layout, context) : renderSparklineAnsi(layout, context.theme);
|
|
138
|
+
const measured = measureLines(output);
|
|
139
|
+
return {
|
|
140
|
+
output,
|
|
141
|
+
actualWidth: measured.maxWidth,
|
|
142
|
+
lineCount: measured.lineCount
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function createSparkline() {
|
|
147
|
+
return new SparklineComponent();
|
|
148
|
+
}
|
|
149
|
+
registry.register(createSparkline);
|
|
150
|
+
export {
|
|
151
|
+
HEIGHT_BLOCKS,
|
|
152
|
+
SparklineComponent,
|
|
153
|
+
computeSparklineLayout,
|
|
154
|
+
createSparkline,
|
|
155
|
+
renderSparklineAnsi,
|
|
156
|
+
renderSparklineMarkdown,
|
|
157
|
+
sparklineInputSchema
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sparkline.ts","../src/schema.ts","../src/layout.ts","../src/renderers.ts"],"sourcesContent":["import {\n BaseTuiComponent,\n measureLines,\n registry,\n type ComponentMetadata,\n type RenderContext,\n type RenderResult,\n} from \"@tuicomponents/core\";\nimport { sparklineInputSchema, type SparklineInput } from \"./schema.js\";\nimport { computeSparklineLayout } from \"./layout.js\";\nimport { renderSparklineAnsi, renderSparklineMarkdown } from \"./renderers.js\";\n\n/**\n * Sparkline component for compact inline data visualization.\n *\n * Renders numeric data as a row of height block characters: ▁▂▃▄▅▆▇█\n *\n * @example\n * ```ts\n * const sparkline = createSparkline();\n * const result = sparkline.render(\n * { values: [1, 3, 5, 7, 6, 4, 2], label: \"Trend: \" },\n * context\n * );\n * // Output: \"Trend: ▁▃▅▇▆▄▂\"\n * ```\n */\nexport class SparklineComponent extends BaseTuiComponent<\n SparklineInput,\n typeof sparklineInputSchema\n> {\n readonly metadata: ComponentMetadata<SparklineInput> = {\n name: \"sparkline\",\n description:\n \"Compact inline sparkline visualization using height block characters (▁▂▃▄▅▆▇█)\",\n version: \"0.1.0\",\n supportedModes: [\"ansi\", \"markdown\"],\n examples: [\n {\n name: \"basic\",\n description: \"Simple ascending values\",\n input: { values: [1, 2, 3, 4, 5, 6, 7, 8] },\n },\n {\n name: \"with-label\",\n description: \"Sparkline with a label prefix\",\n input: { values: [10, 25, 40, 35, 50, 45, 60], label: \"Revenue: \" },\n },\n {\n name: \"compressed\",\n description: \"Data compressed to fewer columns\",\n input: {\n values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n width: 6,\n },\n },\n {\n name: \"explicit-range\",\n description: \"Values scaled to explicit min/max range\",\n input: {\n values: [50, 60, 70, 65, 75],\n min: 0,\n max: 100,\n },\n },\n ],\n };\n\n readonly schema = sparklineInputSchema;\n\n render(input: SparklineInput, context: RenderContext): RenderResult {\n // Parse input (applies defaults)\n const parsed = this.schema.parse(input);\n\n // Compute layout\n const layout = computeSparklineLayout(parsed);\n\n // Render based on mode\n const output =\n context.renderMode === \"markdown\"\n ? renderSparklineMarkdown(layout, context)\n : renderSparklineAnsi(layout, context.theme);\n\n // Measure output\n const measured = measureLines(output);\n\n return {\n output,\n actualWidth: measured.maxWidth,\n lineCount: measured.lineCount,\n };\n }\n}\n\n/**\n * Factory function to create a SparklineComponent instance.\n */\nexport function createSparkline(): SparklineComponent {\n return new SparklineComponent();\n}\n\n// Auto-register with global registry\nregistry.register(createSparkline);\n","import { z } from \"zod\";\n\n/**\n * Input schema for the sparkline component.\n */\nexport const sparklineInputSchema = z.object({\n /** Array of numeric data points to visualize (at least 1 required) */\n values: z.array(z.number()).min(1),\n /** Compress output to this width if less than values.length */\n width: z.number().int().positive().optional(),\n /** Explicit minimum value for scaling (defaults to min of values) */\n min: z.number().optional(),\n /** Explicit maximum value for scaling (defaults to max of values) */\n max: z.number().optional(),\n /** Optional label prefix (e.g., \"CPU: \") */\n label: z.string().optional(),\n});\n\n/**\n * Input type for the sparkline component (before defaults applied).\n */\nexport type SparklineInput = z.input<typeof sparklineInputSchema>;\n\n/**\n * Input type for the sparkline component (after defaults applied).\n */\nexport type SparklineInputWithDefaults = z.output<typeof sparklineInputSchema>;\n","import type { SparklineInputWithDefaults } from \"./schema.js\";\n\n/**\n * Height block characters from lowest to highest.\n * Index 0 is the shortest bar, index 7 is the tallest.\n */\nexport const HEIGHT_BLOCKS = [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\"] as const;\n\n/**\n * Middle block index (used when all values are equal).\n */\nconst MIDDLE_BLOCK_INDEX = 4;\n\n/**\n * Layout information for a sparkline.\n */\nexport interface SparklineLayout {\n /** The computed sparkline string (blocks only) */\n blocks: string;\n /** Optional label prefix */\n label: string | undefined;\n /** Total visual width of the output */\n totalWidth: number;\n}\n\n/**\n * Map a value to a height block index (0-7).\n *\n * @param value - The value to map\n * @param min - Minimum value in the range\n * @param max - Maximum value in the range\n * @returns Block index (0-7)\n */\nfunction valueToBlockIndex(value: number, min: number, max: number): number {\n // If min === max, all values are equal, use middle block\n if (max === min) {\n return MIDDLE_BLOCK_INDEX;\n }\n\n // Normalize value to 0-1 range\n const normalized = (value - min) / (max - min);\n\n // Scale to 0-7 range and clamp\n // We use Math.floor and then clamp to ensure we get 0-7\n const index = Math.floor(normalized * 8);\n\n // Clamp to valid range (handles edge cases like value === max)\n return Math.max(0, Math.min(7, index));\n}\n\n/**\n * Bucket values into groups and return averaged values.\n *\n * @param values - Array of values to bucket\n * @param targetWidth - Target number of buckets\n * @returns Array of averaged values\n */\nfunction bucketValues(values: number[], targetWidth: number): number[] {\n if (targetWidth >= values.length) {\n return values;\n }\n\n const result: number[] = [];\n const bucketSize = values.length / targetWidth;\n\n for (let i = 0; i < targetWidth; i++) {\n const startIndex = Math.floor(i * bucketSize);\n const endIndex = Math.floor((i + 1) * bucketSize);\n\n // Calculate average for this bucket\n let sum = 0;\n let count = 0;\n for (let j = startIndex; j < endIndex && j < values.length; j++) {\n const value = values[j];\n if (value !== undefined) {\n sum += value;\n count++;\n }\n }\n\n result.push(count > 0 ? sum / count : 0);\n }\n\n return result;\n}\n\n/**\n * Compute the layout for a sparkline.\n *\n * @param input - Validated sparkline input\n * @returns Layout information\n */\nexport function computeSparklineLayout(\n input: SparklineInputWithDefaults\n): SparklineLayout {\n const { values, width, min: explicitMin, max: explicitMax, label } = input;\n\n // Apply width compression if needed\n const displayValues =\n width !== undefined && width < values.length\n ? bucketValues(values, width)\n : values;\n\n // Determine min/max for scaling\n const actualMin = Math.min(...displayValues);\n const actualMax = Math.max(...displayValues);\n const min = explicitMin ?? actualMin;\n const max = explicitMax ?? actualMax;\n\n // Convert values to block characters\n const blocks = displayValues\n .map((v) => {\n // Clamp value to explicit min/max range if provided\n const clampedValue = Math.max(min, Math.min(max, v));\n const index = valueToBlockIndex(clampedValue, min, max);\n return HEIGHT_BLOCKS[index];\n })\n .join(\"\");\n\n // Calculate total width\n const labelWidth = label?.length ?? 0;\n const blocksWidth = displayValues.length;\n const totalWidth = labelWidth + blocksWidth;\n\n return {\n blocks,\n label,\n totalWidth,\n };\n}\n","import {\n anchorLine,\n DEFAULT_ANCHOR,\n type RenderContext,\n} from \"@tuicomponents/core\";\nimport type { TuiTheme } from \"@tuicomponents/core\";\nimport type { SparklineLayout } from \"./layout.js\";\n\n/**\n * Render a sparkline in ANSI mode.\n *\n * @param layout - Computed sparkline layout\n * @param theme - Optional theme for styling\n * @returns Rendered ANSI string\n */\nexport function renderSparklineAnsi(\n layout: SparklineLayout,\n theme?: TuiTheme\n): string {\n const { blocks, label } = layout;\n\n // Apply theme if available\n const styledBlocks = theme ? theme.semantic.primary(blocks) : blocks;\n const styledLabel = label\n ? theme\n ? theme.semantic.header(label)\n : label\n : \"\";\n\n return styledLabel + styledBlocks;\n}\n\n/**\n * Render a sparkline in markdown mode.\n *\n * Uses context.style.secondary() to apply semantic styling to the blocks,\n * which wraps them in backticks for visual distinction in markdown renderers.\n * The anchor character is prepended to preserve leading whitespace.\n *\n * @param layout - Computed sparkline layout\n * @param context - Render context for styling\n * @returns Rendered markdown string\n */\nexport function renderSparklineMarkdown(\n layout: SparklineLayout,\n context: RenderContext\n): string {\n const { blocks, label } = layout;\n\n // Apply secondary styling to the data blocks\n // In markdown mode, this wraps in backticks for visual distinction\n const styledBlocks = context.style.secondary(blocks);\n const styledLabel = label ?? \"\";\n\n // Add anchor to preserve leading whitespace in markdown\n return anchorLine(styledLabel + styledBlocks, DEFAULT_ANCHOR);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tuicomponents/sparkline",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Compact inline sparkline visualization using height blocks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"tui",
|
|
26
|
+
"terminal",
|
|
27
|
+
"sparkline",
|
|
28
|
+
"chart",
|
|
29
|
+
"visualization"
|
|
30
|
+
],
|
|
31
|
+
"license": "UNLICENSED",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"zod": "^3.25.56",
|
|
34
|
+
"zod-to-json-schema": "^3.24.5",
|
|
35
|
+
"@tuicomponents/core": "0.1.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "eslint src",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"api-report": "api-extractor run",
|
|
47
|
+
"api-report:update": "api-extractor run --local",
|
|
48
|
+
"clean": "rm -rf dist"
|
|
49
|
+
}
|
|
50
|
+
}
|