chore: initial package split from monorepo
Package: @lilith/chart-math Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
This commit is contained in:
commit
ad912d4504
25 changed files with 739 additions and 0 deletions
10
.forgejo/workflows/publish.yml
Normal file
10
.forgejo/workflows/publish.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
name: Publish
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
publish:
|
||||
uses: lilith/workflows/.forgejo/workflows/publish-npm.yml@main
|
||||
secrets: inherit
|
||||
|
||||
39
dist/catmull-rom.d.ts
vendored
Normal file
39
dist/catmull-rom.d.ts
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Catmull-Rom spline → SVG cubic bezier conversion utilities.
|
||||
*
|
||||
* Generates C1-continuous curves through all control points,
|
||||
* producing smoother interpolation than pairwise bezier.
|
||||
*/
|
||||
export interface Pt2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
/**
|
||||
* Generate SVG cubic bezier C commands for a Catmull-Rom spline through the given points.
|
||||
* Does NOT include the initial M command — only the C segments.
|
||||
*
|
||||
* @param pts - Control points (minimum 2)
|
||||
* @param tension - Spline tension (0 = linear, 0.5 = standard Catmull-Rom, 1 = tight)
|
||||
* @returns SVG path string containing only C commands
|
||||
*/
|
||||
export declare function catmullRomSegments(pts: Pt2[], tension?: number): string;
|
||||
/**
|
||||
* Generate a complete SVG path (M + C commands) for a Catmull-Rom spline.
|
||||
*
|
||||
* @param pts - Control points (minimum 2)
|
||||
* @param tension - Spline tension (default 0.5)
|
||||
* @returns Complete SVG path string starting with M
|
||||
*/
|
||||
export declare function catmullRomPath(pts: Pt2[], tension?: number): string;
|
||||
/**
|
||||
* Generate a closed SVG area path for a stream ribbon.
|
||||
* Traces the top edge left-to-right, then the bottom edge right-to-left,
|
||||
* both as Catmull-Rom splines, closing with Z.
|
||||
*
|
||||
* @param top - Upper boundary points (left-to-right)
|
||||
* @param bottom - Lower boundary points (left-to-right, will be reversed internally)
|
||||
* @param tension - Spline tension (default 0.5)
|
||||
* @returns Closed SVG path string
|
||||
*/
|
||||
export declare function catmullRomAreaPath(top: Pt2[], bottom: Pt2[], tension?: number): string;
|
||||
//# sourceMappingURL=catmull-rom.d.ts.map
|
||||
1
dist/catmull-rom.d.ts.map
vendored
Normal file
1
dist/catmull-rom.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"catmull-rom.d.ts","sourceRoot":"","sources":["../src/catmull-rom.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,SAAM,GAAG,MAAM,CAqBpE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,SAAM,GAAG,MAAM,CAGhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,OAAO,SAAM,GAAG,MAAM,CAQnF"}
|
||||
62
dist/catmull-rom.js
vendored
Normal file
62
dist/catmull-rom.js
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Catmull-Rom spline → SVG cubic bezier conversion utilities.
|
||||
*
|
||||
* Generates C1-continuous curves through all control points,
|
||||
* producing smoother interpolation than pairwise bezier.
|
||||
*/
|
||||
/**
|
||||
* Generate SVG cubic bezier C commands for a Catmull-Rom spline through the given points.
|
||||
* Does NOT include the initial M command — only the C segments.
|
||||
*
|
||||
* @param pts - Control points (minimum 2)
|
||||
* @param tension - Spline tension (0 = linear, 0.5 = standard Catmull-Rom, 1 = tight)
|
||||
* @returns SVG path string containing only C commands
|
||||
*/
|
||||
export function catmullRomSegments(pts, tension = 0.5) {
|
||||
if (pts.length < 2)
|
||||
return '';
|
||||
const out = [];
|
||||
for (let i = 0; i < pts.length - 1; i++) {
|
||||
const p0 = pts[Math.max(0, i - 1)];
|
||||
const p1 = pts[i];
|
||||
const p2 = pts[i + 1];
|
||||
const p3 = pts[Math.min(pts.length - 1, i + 2)];
|
||||
const c1x = p1.x + (tension * (p2.x - p0.x)) / 3;
|
||||
const c1y = p1.y + (tension * (p2.y - p0.y)) / 3;
|
||||
const c2x = p2.x - (tension * (p3.x - p1.x)) / 3;
|
||||
const c2y = p2.y - (tension * (p3.y - p1.y)) / 3;
|
||||
out.push(`C ${c1x.toFixed(1)} ${c1y.toFixed(1)},${c2x.toFixed(1)} ${c2y.toFixed(1)},${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`);
|
||||
}
|
||||
return out.join(' ');
|
||||
}
|
||||
/**
|
||||
* Generate a complete SVG path (M + C commands) for a Catmull-Rom spline.
|
||||
*
|
||||
* @param pts - Control points (minimum 2)
|
||||
* @param tension - Spline tension (default 0.5)
|
||||
* @returns Complete SVG path string starting with M
|
||||
*/
|
||||
export function catmullRomPath(pts, tension = 0.5) {
|
||||
if (pts.length < 2)
|
||||
return '';
|
||||
return `M ${pts[0].x.toFixed(1)} ${pts[0].y.toFixed(1)} ${catmullRomSegments(pts, tension)}`;
|
||||
}
|
||||
/**
|
||||
* Generate a closed SVG area path for a stream ribbon.
|
||||
* Traces the top edge left-to-right, then the bottom edge right-to-left,
|
||||
* both as Catmull-Rom splines, closing with Z.
|
||||
*
|
||||
* @param top - Upper boundary points (left-to-right)
|
||||
* @param bottom - Lower boundary points (left-to-right, will be reversed internally)
|
||||
* @param tension - Spline tension (default 0.5)
|
||||
* @returns Closed SVG path string
|
||||
*/
|
||||
export function catmullRomAreaPath(top, bottom, tension = 0.5) {
|
||||
if (top.length < 2 || bottom.length < 2)
|
||||
return '';
|
||||
const reversed = [...bottom].reverse();
|
||||
const topPath = catmullRomPath(top, tension);
|
||||
const bottomSegs = catmullRomSegments(reversed, tension);
|
||||
return `${topPath} L ${reversed[0].x.toFixed(1)} ${reversed[0].y.toFixed(1)} ${bottomSegs} Z`;
|
||||
}
|
||||
//# sourceMappingURL=catmull-rom.js.map
|
||||
1
dist/catmull-rom.js.map
vendored
Normal file
1
dist/catmull-rom.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"catmull-rom.js","sourceRoot":"","sources":["../src/catmull-rom.ts"],"names":[],"mappings":"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"}
|
||||
94
dist/chart.d.ts
vendored
Normal file
94
dist/chart.d.ts
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Pure math utilities for SVG chart rendering.
|
||||
* Scales, path generation, sparklines, and axis computation.
|
||||
*/
|
||||
export interface ChartDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
padding: number;
|
||||
chartWidth: number;
|
||||
chartHeight: number;
|
||||
}
|
||||
export interface ScaleConfig {
|
||||
min: number;
|
||||
max: number;
|
||||
range: number;
|
||||
}
|
||||
/**
|
||||
* Calculate chart dimensions with padding
|
||||
*/
|
||||
export declare function calculateChartDimensions(width: number, height: number, padding: number): ChartDimensions;
|
||||
/**
|
||||
* Calculate scale configuration for a set of values
|
||||
*/
|
||||
export declare function calculateScale(values: number[], includeZero?: boolean): ScaleConfig;
|
||||
/**
|
||||
* Create a linear scale function
|
||||
*/
|
||||
export declare function createLinearScale(domain: [number, number], range: [number, number]): (value: number) => number;
|
||||
/**
|
||||
* Generate evenly spaced tick values
|
||||
*/
|
||||
export declare function generateTicks(min: number, max: number, count: number): number[];
|
||||
/**
|
||||
* Generate SVG path for line chart
|
||||
*/
|
||||
export declare function generateLinePath(points: Array<{
|
||||
x: number;
|
||||
y: number;
|
||||
}>, curve?: boolean): string;
|
||||
/**
|
||||
* Generate SVG path for area under line
|
||||
*/
|
||||
export declare function generateAreaPath(linePath: string, firstX: number, lastX: number, baselineY: number): string;
|
||||
/**
|
||||
* Calculate points for a sparkline
|
||||
*/
|
||||
export declare function calculateSparklinePoints(data: number[], width: number, height: number): Array<{
|
||||
x: number;
|
||||
y: number;
|
||||
}>;
|
||||
/**
|
||||
* Format timestamp as HH:MM:SS for chart axis labels
|
||||
*
|
||||
* @param timestamp - Unix timestamp in milliseconds
|
||||
* @returns Formatted time string (e.g., "14:30:05")
|
||||
*/
|
||||
export declare function formatChartTime(timestamp: number): string;
|
||||
/**
|
||||
* Auto-scale configuration for Y-axis
|
||||
*/
|
||||
export interface AutoYScaleConfig {
|
||||
/** Minimum value on the axis */
|
||||
min: number;
|
||||
/** Maximum value on the axis */
|
||||
max: number;
|
||||
/** Range (max - min) */
|
||||
range: number;
|
||||
}
|
||||
/**
|
||||
* Calculate auto-scaling Y-axis bounds based on visible data with padding.
|
||||
* Automatically adds 10% padding above and below the data range.
|
||||
*
|
||||
* @param values - Array of numeric values to calculate scale for
|
||||
* @param options - Optional configuration
|
||||
* @param options.paddingPercent - Padding percentage (default: 0.1 = 10%)
|
||||
* @param options.minPadding - Minimum padding in data units (default: 5)
|
||||
* @param options.includeZero - Whether to always include zero in the scale (default: false)
|
||||
* @returns Scale configuration with min, max, and range
|
||||
*/
|
||||
export declare function calculateAutoYScale(values: number[], options?: {
|
||||
paddingPercent?: number;
|
||||
minPadding?: number;
|
||||
includeZero?: boolean;
|
||||
}): AutoYScaleConfig;
|
||||
/**
|
||||
* Get color from chart series palette by index.
|
||||
* Cycles through palette if index exceeds palette length.
|
||||
*
|
||||
* @param index - Zero-based index of the data series
|
||||
* @param palette - Array of color hex strings
|
||||
* @returns Hex color string
|
||||
*/
|
||||
export declare function getSeriesColor(index: number, palette: readonly string[]): string;
|
||||
//# sourceMappingURL=chart.d.ts.map
|
||||
1
dist/chart.d.ts.map
vendored
Normal file
1
dist/chart.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../src/chart.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,eAAe,CAQjB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,UAAO,GAAG,WAAW,CAMhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACxB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAW3B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,KAAK,UAAQ,GAAG,MAAM,CAwB/F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAajC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,GAAE;IACP,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CAClB,GACL,gBAAgB,CAmClB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAKhF"}
|
||||
155
dist/chart.js
vendored
Normal file
155
dist/chart.js
vendored
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* Pure math utilities for SVG chart rendering.
|
||||
* Scales, path generation, sparklines, and axis computation.
|
||||
*/
|
||||
/**
|
||||
* Calculate chart dimensions with padding
|
||||
*/
|
||||
export function calculateChartDimensions(width, height, padding) {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
chartWidth: width - padding * 2,
|
||||
chartHeight: height - padding * 2,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Calculate scale configuration for a set of values
|
||||
*/
|
||||
export function calculateScale(values, includeZero = true) {
|
||||
const min = includeZero ? Math.min(...values, 0) : Math.min(...values);
|
||||
const max = Math.max(...values);
|
||||
const range = max - min || 1;
|
||||
return { min, max, range };
|
||||
}
|
||||
/**
|
||||
* Create a linear scale function
|
||||
*/
|
||||
export function createLinearScale(domain, range) {
|
||||
const [domainMin, domainMax] = domain;
|
||||
const [rangeMin, rangeMax] = range;
|
||||
const domainRange = domainMax - domainMin || 1;
|
||||
const rangeRange = rangeMax - rangeMin;
|
||||
return (value) => {
|
||||
const normalized = (value - domainMin) / domainRange;
|
||||
return rangeMin + normalized * rangeRange;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Generate evenly spaced tick values
|
||||
*/
|
||||
export function generateTicks(min, max, count) {
|
||||
const step = (max - min) / (count - 1);
|
||||
return Array.from({ length: count }, (_, i) => min + step * i);
|
||||
}
|
||||
/**
|
||||
* Generate SVG path for line chart
|
||||
*/
|
||||
export function generateLinePath(points, curve = false) {
|
||||
if (points.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const pathSegments = points.map((point, i) => {
|
||||
if (i === 0) {
|
||||
return `M ${point.x} ${point.y}`;
|
||||
}
|
||||
if (curve && i > 0) {
|
||||
const prev = points[i - 1];
|
||||
const cp1x = prev.x + (point.x - prev.x) / 3;
|
||||
const cp1y = prev.y;
|
||||
const cp2x = point.x - (point.x - prev.x) / 3;
|
||||
const cp2y = point.y;
|
||||
return `C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${point.x} ${point.y}`;
|
||||
}
|
||||
return `L ${point.x} ${point.y}`;
|
||||
});
|
||||
return pathSegments.join(' ');
|
||||
}
|
||||
/**
|
||||
* Generate SVG path for area under line
|
||||
*/
|
||||
export function generateAreaPath(linePath, firstX, lastX, baselineY) {
|
||||
return `${linePath} L ${lastX} ${baselineY} L ${firstX} ${baselineY} Z`;
|
||||
}
|
||||
/**
|
||||
* Calculate points for a sparkline
|
||||
*/
|
||||
export function calculateSparklinePoints(data, width, height) {
|
||||
if (data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const scale = calculateScale(data, false);
|
||||
const xScale = createLinearScale([0, data.length - 1], [0, width]);
|
||||
const yScale = createLinearScale([scale.min, scale.max], [height, 0]);
|
||||
return data.map((value, index) => ({
|
||||
x: xScale(index),
|
||||
y: yScale(value),
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Format timestamp as HH:MM:SS for chart axis labels
|
||||
*
|
||||
* @param timestamp - Unix timestamp in milliseconds
|
||||
* @returns Formatted time string (e.g., "14:30:05")
|
||||
*/
|
||||
export function formatChartTime(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = date.getSeconds().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
/**
|
||||
* Calculate auto-scaling Y-axis bounds based on visible data with padding.
|
||||
* Automatically adds 10% padding above and below the data range.
|
||||
*
|
||||
* @param values - Array of numeric values to calculate scale for
|
||||
* @param options - Optional configuration
|
||||
* @param options.paddingPercent - Padding percentage (default: 0.1 = 10%)
|
||||
* @param options.minPadding - Minimum padding in data units (default: 5)
|
||||
* @param options.includeZero - Whether to always include zero in the scale (default: false)
|
||||
* @returns Scale configuration with min, max, and range
|
||||
*/
|
||||
export function calculateAutoYScale(values, options = {}) {
|
||||
const { paddingPercent = 0.1, minPadding = 5, includeZero = false, } = options;
|
||||
// Default scale when no data
|
||||
if (values.length === 0) {
|
||||
return { min: 0, max: 100, range: 100 };
|
||||
}
|
||||
const dataMin = Math.min(...values);
|
||||
const dataMax = Math.max(...values);
|
||||
const dataRange = dataMax - dataMin;
|
||||
// Calculate padding (percentage of range, or minimum padding if range is small)
|
||||
const padding = Math.max(dataRange * paddingPercent, minPadding);
|
||||
let min = Math.floor(dataMin - padding);
|
||||
const max = Math.ceil(dataMax + padding);
|
||||
// Optionally include zero in the scale
|
||||
if (includeZero) {
|
||||
min = Math.min(min, 0);
|
||||
}
|
||||
else {
|
||||
// Never go below zero for typical chart data
|
||||
min = Math.max(0, min);
|
||||
}
|
||||
return {
|
||||
min,
|
||||
max,
|
||||
range: max - min,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get color from chart series palette by index.
|
||||
* Cycles through palette if index exceeds palette length.
|
||||
*
|
||||
* @param index - Zero-based index of the data series
|
||||
* @param palette - Array of color hex strings
|
||||
* @returns Hex color string
|
||||
*/
|
||||
export function getSeriesColor(index, palette) {
|
||||
if (palette.length === 0) {
|
||||
return '#3B82F6'; // Default blue
|
||||
}
|
||||
return palette[index % palette.length] ?? palette[0];
|
||||
}
|
||||
//# sourceMappingURL=chart.js.map
|
||||
1
dist/chart.js.map
vendored
Normal file
1
dist/chart.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"chart.js","sourceRoot":"","sources":["../src/chart.ts"],"names":[],"mappings":"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"}
|
||||
3
dist/index.d.ts
vendored
Normal file
3
dist/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './chart.ts';
|
||||
export * from './catmull-rom.ts';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
dist/index.d.ts.map
vendored
Normal file
1
dist/index.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
|
||||
3
dist/index.js
vendored
Normal file
3
dist/index.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./chart.js";
|
||||
export * from "./catmull-rom.js";
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
|
||||
1
node_modules/.bin/vitest
generated
vendored
Symbolic link
1
node_modules/.bin/vitest
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../vitest/vitest.mjs
|
||||
1
node_modules/@types/react
generated
vendored
Symbolic link
1
node_modules/@types/react
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.bun/@types+react@19.2.14/node_modules/@types/react
|
||||
1
node_modules/@types/react-dom
generated
vendored
Symbolic link
1
node_modules/@types/react-dom
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.bun/@types+react-dom@19.2.3+273cdfb19a04c3e9/node_modules/@types/react-dom
|
||||
1
node_modules/react
generated
vendored
Symbolic link
1
node_modules/react
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/react@19.2.5/node_modules/react
|
||||
1
node_modules/react-dom
generated
vendored
Symbolic link
1
node_modules/react-dom
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom
|
||||
1
node_modules/styled-components
generated
vendored
Symbolic link
1
node_modules/styled-components
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/styled-components@6.4.0+21ccd8898788a04d/node_modules/styled-components
|
||||
1
node_modules/vitest
generated
vendored
Symbolic link
1
node_modules/vitest
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.bun/vitest@2.1.9+1f2148f19abc7b8c/node_modules/vitest
|
||||
39
package.json
Normal file
39
package.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "@lilith/chart-math",
|
||||
"version": "1.0.1",
|
||||
"description": "Pure math utilities for SVG chart rendering — scales, paths, sparklines, Catmull-Rom splines",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --fix",
|
||||
"lint:check": "eslint src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"vitest": "^2.1.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0",
|
||||
"styled-components": "^6.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "http://forge.black.local/api/packages/lilith/npm/"
|
||||
},
|
||||
"_": {
|
||||
"registry": "forgejo",
|
||||
"publish": true,
|
||||
"build": true
|
||||
}
|
||||
}
|
||||
74
src/catmull-rom.ts
Normal file
74
src/catmull-rom.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Catmull-Rom spline → SVG cubic bezier conversion utilities.
|
||||
*
|
||||
* Generates C1-continuous curves through all control points,
|
||||
* producing smoother interpolation than pairwise bezier.
|
||||
*/
|
||||
|
||||
export interface Pt2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SVG cubic bezier C commands for a Catmull-Rom spline through the given points.
|
||||
* Does NOT include the initial M command — only the C segments.
|
||||
*
|
||||
* @param pts - Control points (minimum 2)
|
||||
* @param tension - Spline tension (0 = linear, 0.5 = standard Catmull-Rom, 1 = tight)
|
||||
* @returns SVG path string containing only C commands
|
||||
*/
|
||||
export function catmullRomSegments(pts: Pt2[], tension = 0.5): string {
|
||||
if (pts.length < 2) return '';
|
||||
|
||||
const out: string[] = [];
|
||||
for (let i = 0; i < pts.length - 1; i++) {
|
||||
const p0 = pts[Math.max(0, i - 1)];
|
||||
const p1 = pts[i];
|
||||
const p2 = pts[i + 1];
|
||||
const p3 = pts[Math.min(pts.length - 1, i + 2)];
|
||||
|
||||
const c1x = p1.x + (tension * (p2.x - p0.x)) / 3;
|
||||
const c1y = p1.y + (tension * (p2.y - p0.y)) / 3;
|
||||
const c2x = p2.x - (tension * (p3.x - p1.x)) / 3;
|
||||
const c2y = p2.y - (tension * (p3.y - p1.y)) / 3;
|
||||
|
||||
out.push(
|
||||
`C ${c1x.toFixed(1)} ${c1y.toFixed(1)},${c2x.toFixed(1)} ${c2y.toFixed(1)},${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a complete SVG path (M + C commands) for a Catmull-Rom spline.
|
||||
*
|
||||
* @param pts - Control points (minimum 2)
|
||||
* @param tension - Spline tension (default 0.5)
|
||||
* @returns Complete SVG path string starting with M
|
||||
*/
|
||||
export function catmullRomPath(pts: Pt2[], tension = 0.5): string {
|
||||
if (pts.length < 2) return '';
|
||||
return `M ${pts[0].x.toFixed(1)} ${pts[0].y.toFixed(1)} ${catmullRomSegments(pts, tension)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a closed SVG area path for a stream ribbon.
|
||||
* Traces the top edge left-to-right, then the bottom edge right-to-left,
|
||||
* both as Catmull-Rom splines, closing with Z.
|
||||
*
|
||||
* @param top - Upper boundary points (left-to-right)
|
||||
* @param bottom - Lower boundary points (left-to-right, will be reversed internally)
|
||||
* @param tension - Spline tension (default 0.5)
|
||||
* @returns Closed SVG path string
|
||||
*/
|
||||
export function catmullRomAreaPath(top: Pt2[], bottom: Pt2[], tension = 0.5): string {
|
||||
if (top.length < 2 || bottom.length < 2) return '';
|
||||
|
||||
const reversed = [...bottom].reverse();
|
||||
const topPath = catmullRomPath(top, tension);
|
||||
const bottomSegs = catmullRomSegments(reversed, tension);
|
||||
|
||||
return `${topPath} L ${reversed[0].x.toFixed(1)} ${reversed[0].y.toFixed(1)} ${bottomSegs} Z`;
|
||||
}
|
||||
233
src/chart.ts
Normal file
233
src/chart.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
* Pure math utilities for SVG chart rendering.
|
||||
* Scales, path generation, sparklines, and axis computation.
|
||||
*/
|
||||
|
||||
export interface ChartDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
padding: number;
|
||||
chartWidth: number;
|
||||
chartHeight: number;
|
||||
}
|
||||
|
||||
export interface ScaleConfig {
|
||||
min: number;
|
||||
max: number;
|
||||
range: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate chart dimensions with padding
|
||||
*/
|
||||
export function calculateChartDimensions(
|
||||
width: number,
|
||||
height: number,
|
||||
padding: number,
|
||||
): ChartDimensions {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
chartWidth: width - padding * 2,
|
||||
chartHeight: height - padding * 2,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate scale configuration for a set of values
|
||||
*/
|
||||
export function calculateScale(values: number[], includeZero = true): ScaleConfig {
|
||||
const min = includeZero ? Math.min(...values, 0) : Math.min(...values);
|
||||
const max = Math.max(...values);
|
||||
const range = max - min || 1;
|
||||
|
||||
return { min, max, range };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a linear scale function
|
||||
*/
|
||||
export function createLinearScale(
|
||||
domain: [number, number],
|
||||
range: [number, number],
|
||||
): (value: number) => number {
|
||||
const [domainMin, domainMax] = domain;
|
||||
const [rangeMin, rangeMax] = range;
|
||||
const domainRange = domainMax - domainMin || 1;
|
||||
const rangeRange = rangeMax - rangeMin;
|
||||
|
||||
return (value: number) => {
|
||||
const normalized = (value - domainMin) / domainRange;
|
||||
|
||||
return rangeMin + normalized * rangeRange;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate evenly spaced tick values
|
||||
*/
|
||||
export function generateTicks(min: number, max: number, count: number): number[] {
|
||||
const step = (max - min) / (count - 1);
|
||||
|
||||
return Array.from({ length: count }, (_, i) => min + step * i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SVG path for line chart
|
||||
*/
|
||||
export function generateLinePath(points: Array<{ x: number; y: number }>, curve = false): string {
|
||||
if (points.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const pathSegments = points.map((point, i) => {
|
||||
if (i === 0) {
|
||||
return `M ${point.x} ${point.y}`;
|
||||
}
|
||||
|
||||
if (curve && i > 0) {
|
||||
const prev = points[i - 1];
|
||||
const cp1x = prev.x + (point.x - prev.x) / 3;
|
||||
const cp1y = prev.y;
|
||||
const cp2x = point.x - (point.x - prev.x) / 3;
|
||||
const cp2y = point.y;
|
||||
|
||||
return `C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${point.x} ${point.y}`;
|
||||
}
|
||||
|
||||
return `L ${point.x} ${point.y}`;
|
||||
});
|
||||
|
||||
return pathSegments.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SVG path for area under line
|
||||
*/
|
||||
export function generateAreaPath(
|
||||
linePath: string,
|
||||
firstX: number,
|
||||
lastX: number,
|
||||
baselineY: number,
|
||||
): string {
|
||||
return `${linePath} L ${lastX} ${baselineY} L ${firstX} ${baselineY} Z`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate points for a sparkline
|
||||
*/
|
||||
export function calculateSparklinePoints(
|
||||
data: number[],
|
||||
width: number,
|
||||
height: number,
|
||||
): Array<{ x: number; y: number }> {
|
||||
if (data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const scale = calculateScale(data, false);
|
||||
const xScale = createLinearScale([0, data.length - 1], [0, width]);
|
||||
const yScale = createLinearScale([scale.min, scale.max], [height, 0]);
|
||||
|
||||
return data.map((value, index) => ({
|
||||
x: xScale(index),
|
||||
y: yScale(value),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format timestamp as HH:MM:SS for chart axis labels
|
||||
*
|
||||
* @param timestamp - Unix timestamp in milliseconds
|
||||
* @returns Formatted time string (e.g., "14:30:05")
|
||||
*/
|
||||
export function formatChartTime(timestamp: number): string {
|
||||
const date = new Date(timestamp);
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = date.getSeconds().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-scale configuration for Y-axis
|
||||
*/
|
||||
export interface AutoYScaleConfig {
|
||||
/** Minimum value on the axis */
|
||||
min: number;
|
||||
/** Maximum value on the axis */
|
||||
max: number;
|
||||
/** Range (max - min) */
|
||||
range: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate auto-scaling Y-axis bounds based on visible data with padding.
|
||||
* Automatically adds 10% padding above and below the data range.
|
||||
*
|
||||
* @param values - Array of numeric values to calculate scale for
|
||||
* @param options - Optional configuration
|
||||
* @param options.paddingPercent - Padding percentage (default: 0.1 = 10%)
|
||||
* @param options.minPadding - Minimum padding in data units (default: 5)
|
||||
* @param options.includeZero - Whether to always include zero in the scale (default: false)
|
||||
* @returns Scale configuration with min, max, and range
|
||||
*/
|
||||
export function calculateAutoYScale(
|
||||
values: number[],
|
||||
options: {
|
||||
paddingPercent?: number;
|
||||
minPadding?: number;
|
||||
includeZero?: boolean;
|
||||
} = {},
|
||||
): AutoYScaleConfig {
|
||||
const {
|
||||
paddingPercent = 0.1,
|
||||
minPadding = 5,
|
||||
includeZero = false,
|
||||
} = options;
|
||||
|
||||
// Default scale when no data
|
||||
if (values.length === 0) {
|
||||
return { min: 0, max: 100, range: 100 };
|
||||
}
|
||||
|
||||
const dataMin = Math.min(...values);
|
||||
const dataMax = Math.max(...values);
|
||||
const dataRange = dataMax - dataMin;
|
||||
|
||||
// Calculate padding (percentage of range, or minimum padding if range is small)
|
||||
const padding = Math.max(dataRange * paddingPercent, minPadding);
|
||||
|
||||
let min = Math.floor(dataMin - padding);
|
||||
const max = Math.ceil(dataMax + padding);
|
||||
|
||||
// Optionally include zero in the scale
|
||||
if (includeZero) {
|
||||
min = Math.min(min, 0);
|
||||
} else {
|
||||
// Never go below zero for typical chart data
|
||||
min = Math.max(0, min);
|
||||
}
|
||||
|
||||
return {
|
||||
min,
|
||||
max,
|
||||
range: max - min,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color from chart series palette by index.
|
||||
* Cycles through palette if index exceeds palette length.
|
||||
*
|
||||
* @param index - Zero-based index of the data series
|
||||
* @param palette - Array of color hex strings
|
||||
* @returns Hex color string
|
||||
*/
|
||||
export function getSeriesColor(index: number, palette: readonly string[]): string {
|
||||
if (palette.length === 0) {
|
||||
return '#3B82F6'; // Default blue
|
||||
}
|
||||
return palette[index % palette.length] ?? palette[0];
|
||||
}
|
||||
2
src/index.ts
Normal file
2
src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './chart.ts';
|
||||
export * from './catmull-rom.ts';
|
||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"noEmit": false,
|
||||
"rewriteRelativeImportExtensions": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue