247 lines
7.5 KiB
TypeScript
Raw Normal View History

import { Dimensions } from 'hooks/useDimensions';
import { LegendPosition } from 'types/api/dashboard/getAll';
export interface EnhancedLegendConfig {
minHeight: number;
maxHeight: number;
calculatedHeight: number;
showScrollbar: boolean;
requiredRows: number;
// For right-side legend
minWidth?: number;
maxWidth?: number;
calculatedWidth?: number;
}
/**
* Calculate legend configuration based on panel dimensions and series count
* Prioritizes chart space while ensuring legend usability
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
export function calculateEnhancedLegendConfig(
dimensions: Dimensions,
seriesCount: number,
seriesLabels?: string[],
legendPosition: LegendPosition = LegendPosition.BOTTOM,
): EnhancedLegendConfig {
const lineHeight = 34;
const padding = 12;
const maxRowsToShow = 2; // Reduced from 3 to 2 for better chart/legend ratio
// Different configurations for bottom vs right positioning
if (legendPosition === LegendPosition.RIGHT) {
// Right-side legend configuration
const maxLegendWidthRatio = 0.3; // Legend should not take more than 30% of panel width
const absoluteMaxWidth = Math.min(
400,
dimensions.width * maxLegendWidthRatio,
);
const minWidth = 150;
// For right-side legend, calculate based on text length
const avgCharWidth = 8;
let avgTextLength = 15;
if (seriesLabels && seriesLabels.length > 0) {
const totalLength = seriesLabels.reduce(
(sum, label) => sum + Math.min(label.length, 40),
0,
);
avgTextLength = Math.max(
10,
Math.min(35, totalLength / seriesLabels.length),
);
}
// Fix: Ensure width respects the ratio constraint even if it's less than minWidth
const estimatedWidth = 80 + avgCharWidth * avgTextLength;
const calculatedWidth = Math.min(
Math.max(minWidth, estimatedWidth),
absoluteMaxWidth,
);
// For right-side legend, height can be more flexible
const maxHeight = dimensions.height - 40; // Leave some padding
const idealHeight = seriesCount * lineHeight + padding;
const calculatedHeight = Math.min(idealHeight, maxHeight);
const showScrollbar = idealHeight > calculatedHeight;
return {
minHeight: lineHeight + padding,
maxHeight,
calculatedHeight,
showScrollbar,
requiredRows: seriesCount, // Each series on its own row for right-side
minWidth,
maxWidth: absoluteMaxWidth,
calculatedWidth,
};
}
// Bottom legend configuration (existing logic)
const maxLegendRatio = 0.15;
// Fix: For very small dimensions, respect the ratio instead of using fixed 80px minimum
const ratioBasedMaxHeight = dimensions.height * maxLegendRatio;
// Handle edge cases and calculate absolute max height
let absoluteMaxHeight;
if (dimensions.height <= 0) {
absoluteMaxHeight = 46; // Fallback for invalid dimensions
} else if (dimensions.height <= 400) {
// For small to medium panels, prioritize ratio constraint
absoluteMaxHeight = Math.min(80, Math.max(15, ratioBasedMaxHeight));
} else {
// For larger panels, maintain a reasonable minimum
absoluteMaxHeight = Math.min(80, Math.max(20, ratioBasedMaxHeight));
}
const baseItemWidth = 44;
const avgCharWidth = 8;
let avgTextLength = 15;
if (seriesLabels && seriesLabels.length > 0) {
const totalLength = seriesLabels.reduce(
(sum, label) => sum + Math.min(label.length, 30),
0,
);
avgTextLength = Math.max(8, Math.min(25, totalLength / seriesLabels.length));
}
// Estimate item width based on actual or estimated text length
let estimatedItemWidth = baseItemWidth + avgCharWidth * avgTextLength;
// For very wide panels, allow longer text
if (dimensions.width > 800) {
estimatedItemWidth = Math.max(
estimatedItemWidth,
baseItemWidth + avgCharWidth * 22,
);
} else if (dimensions.width < 400) {
estimatedItemWidth = Math.min(
estimatedItemWidth,
baseItemWidth + avgCharWidth * 14,
);
}
// Calculate items per row based on available width
const availableWidth = dimensions.width - padding * 2;
const itemsPerRow = Math.max(
1,
Math.floor(availableWidth / estimatedItemWidth),
);
let requiredRows = Math.ceil(seriesCount / itemsPerRow);
if (requiredRows === 1 && seriesCount > 3) {
requiredRows = 2;
}
// Calculate heights
const idealHeight = requiredRows * lineHeight + padding;
// For single row, use minimal height
let minHeight;
if (requiredRows <= 1) {
minHeight = lineHeight + padding; // Single row
} else {
// Multiple rows: show 2 rows max, then scroll
minHeight = Math.min(2 * lineHeight + padding, idealHeight);
}
// For very small dimensions, allow the minHeight to be smaller to respect ratio constraints
if (dimensions.height < 200) {
minHeight = Math.min(minHeight, absoluteMaxHeight);
}
// Maximum height constraint - prioritize chart space
// Fix: Ensure we respect the ratio-based constraint for small dimensions
const rowBasedMaxHeight = maxRowsToShow * lineHeight + padding;
const maxHeight = Math.min(rowBasedMaxHeight, absoluteMaxHeight);
const calculatedHeight = Math.max(minHeight, Math.min(idealHeight, maxHeight));
const showScrollbar = idealHeight > calculatedHeight;
return {
minHeight,
maxHeight,
calculatedHeight,
showScrollbar,
requiredRows,
};
}
// CSS class constants
const LEGEND_SINGLE_LINE_CLASS = 'u-legend-single-line';
const LEGEND_MULTI_LINE_CLASS = 'u-legend-multi-line';
const LEGEND_RIGHT_ALIGNED_CLASS = 'u-legend-right-aligned';
/**
* Apply enhanced legend styling to a legend element
*/
export function applyEnhancedLegendStyling(
legend: HTMLElement,
config: EnhancedLegendConfig,
requiredRows: number,
legendPosition: LegendPosition = LegendPosition.BOTTOM,
): void {
const legendElement = legend;
legendElement.classList.add('u-legend-enhanced');
// Apply position-specific styling
if (legendPosition === LegendPosition.RIGHT) {
legendElement.classList.add('u-legend-right');
legendElement.classList.remove('u-legend-bottom');
// Set width for right-side legend
if (config.calculatedWidth) {
legendElement.style.width = `${config.calculatedWidth}px`;
legendElement.style.minWidth = `${config.minWidth}px`;
legendElement.style.maxWidth = `${config.maxWidth}px`;
}
// Height for right-side legend
legendElement.style.height = `${config.calculatedHeight}px`;
legendElement.style.minHeight = `${config.minHeight}px`;
legendElement.style.maxHeight = `${config.maxHeight}px`;
} else {
legendElement.classList.add('u-legend-bottom');
legendElement.classList.remove('u-legend-right');
// Height for bottom legend
legendElement.style.height = `${config.calculatedHeight}px`;
legendElement.style.minHeight = `${config.minHeight}px`;
legendElement.style.maxHeight = `${config.maxHeight}px`;
// Reset width for bottom legend
legendElement.style.width = '';
legendElement.style.minWidth = '';
legendElement.style.maxWidth = '';
}
// Apply alignment based on position and number of rows
if (legendPosition === LegendPosition.RIGHT) {
legendElement.classList.add(LEGEND_RIGHT_ALIGNED_CLASS);
legendElement.classList.remove(
LEGEND_SINGLE_LINE_CLASS,
LEGEND_MULTI_LINE_CLASS,
);
} else if (requiredRows === 1) {
legendElement.classList.add(LEGEND_SINGLE_LINE_CLASS);
legendElement.classList.remove(
LEGEND_MULTI_LINE_CLASS,
LEGEND_RIGHT_ALIGNED_CLASS,
);
} else {
legendElement.classList.add(LEGEND_MULTI_LINE_CLASS);
legendElement.classList.remove(
LEGEND_SINGLE_LINE_CLASS,
LEGEND_RIGHT_ALIGNED_CLASS,
);
}
// Add scrollbar indicator if needed
if (config.showScrollbar) {
legendElement.classList.add('u-legend-scrollable');
} else {
legendElement.classList.remove('u-legend-scrollable');
}
}