Package: @lilith/chart-math Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
155 lines
No EOL
5 KiB
JavaScript
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
|