chart-math/dist/chart.js
autocommit ad912d4504 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
2026-04-20 01:10:42 -07:00

155 lines
No EOL
5 KiB
JavaScript

/**
* 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