feat: added enhancements to legends in panel

This commit is contained in:
SagarRajput-7 2025-05-26 03:33:11 +05:30
parent 0925ae73a9
commit 522ee2f4a7
4 changed files with 286 additions and 3 deletions

View File

@ -138,6 +138,7 @@ function UplotPanelWrapper({
timezone: timezone.value,
customSeries,
isLogScale: widget?.isLogScale,
enhancedLegend: true, // Enable enhanced legend
}),
[
widget?.id,

View File

@ -0,0 +1,123 @@
import { Dimensions } from 'hooks/useDimensions';
export interface EnhancedLegendConfig {
minHeight: number;
maxHeight: number;
calculatedHeight: number;
showScrollbar: boolean;
requiredRows: number;
}
/**
* Calculate legend configuration based on panel dimensions and series count
* Prioritizes chart space while ensuring legend usability
*/
export function calculateEnhancedLegendConfig(
dimensions: Dimensions,
seriesCount: number,
seriesLabels?: string[],
): EnhancedLegendConfig {
const lineHeight = 34;
const padding = 12;
const maxRowsToShow = 2; // Reduced from 3 to 2 for better chart/legend ratio
// Legend should not take more than 15% of panel height, with absolute max of 80px
const maxLegendRatio = 0.15;
const absoluteMaxHeight = Math.min(80, dimensions.height * maxLegendRatio);
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),
);
const requiredRows = Math.ceil(seriesCount / itemsPerRow);
// 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);
}
// Maximum height constraint - prioritize chart space
const maxHeight = Math.min(
maxRowsToShow * lineHeight + padding,
absoluteMaxHeight,
);
const calculatedHeight = Math.max(minHeight, Math.min(idealHeight, maxHeight));
const showScrollbar = idealHeight > calculatedHeight;
return {
minHeight,
maxHeight,
calculatedHeight,
showScrollbar,
requiredRows,
};
}
/**
* Apply enhanced legend styling to a legend element
*/
export function applyEnhancedLegendStyling(
legend: HTMLElement,
config: EnhancedLegendConfig,
requiredRows: number,
): void {
const legendElement = legend;
legendElement.classList.add('u-legend-enhanced');
legendElement.style.height = `${config.calculatedHeight}px`;
legendElement.style.minHeight = `${config.minHeight}px`;
legendElement.style.maxHeight = `${config.maxHeight}px`;
// Apply alignment based on number of rows
if (requiredRows === 1) {
legendElement.classList.add('u-legend-single-line');
legendElement.classList.remove('u-legend-multi-line');
} else {
legendElement.classList.add('u-legend-multi-line');
legendElement.classList.remove('u-legend-single-line');
}
// Add scrollbar indicator if needed
if (config.showScrollbar) {
legendElement.classList.add('u-legend-scrollable');
} else {
legendElement.classList.remove('u-legend-scrollable');
}
}

View File

@ -8,8 +8,13 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { FullViewProps } from 'container/GridCardLayout/GridCard/FullView/types';
import { saveLegendEntriesToLocalStorage } from 'container/GridCardLayout/GridCard/FullView/utils';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import {
applyEnhancedLegendStyling,
calculateEnhancedLegendConfig,
} from 'container/PanelWrapper/enhancedLegend';
import { Dimensions } from 'hooks/useDimensions';
import { convertValue } from 'lib/getConvertedValue';
import getLabelName from 'lib/getLabelName';
import { cloneDeep, isUndefined } from 'lodash-es';
import _noop from 'lodash-es/noop';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
@ -60,6 +65,7 @@ export interface GetUPlotChartOptions {
customSeries?: (data: QueryData[]) => uPlot.Series[];
isLogScale?: boolean;
colorMapping?: Record<string, string>;
enhancedLegend?: boolean;
}
/** the function converts series A , series B , series C to
@ -168,6 +174,7 @@ export const getUPlotChartOptions = ({
customSeries,
isLogScale,
colorMapping,
enhancedLegend = true,
}: GetUPlotChartOptions): uPlot.Options => {
const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale);
@ -180,10 +187,27 @@ export const getUPlotChartOptions = ({
const bands = stackBarChart ? getBands(series) : null;
// Calculate dynamic legend height based on panel dimensions and series count
const seriesCount = (apiResponse?.data?.result || []).length;
const seriesLabels = enhancedLegend
? (apiResponse?.data?.result || []).map((item) =>
getLabelName(item.metric || {}, item.queryName || '', item.legend || ''),
)
: [];
const legendConfig = enhancedLegend
? calculateEnhancedLegendConfig(dimensions, seriesCount, seriesLabels)
: {
calculatedHeight: 30,
minHeight: 30,
maxHeight: 30,
itemsPerRow: 3,
showScrollbar: false,
};
return {
id,
width: dimensions.width,
height: dimensions.height - 30,
height: dimensions.height - legendConfig.calculatedHeight - 10, // Adjust chart height for enhanced legend
legend: {
show: true,
live: false,
@ -335,6 +359,15 @@ export const getUPlotChartOptions = ({
(self): void => {
const legend = self.root.querySelector('.u-legend');
if (legend) {
// Apply enhanced legend styling
if (enhancedLegend) {
applyEnhancedLegendStyling(
legend as HTMLElement,
legendConfig,
legendConfig.requiredRows,
);
}
const seriesEls = legend.querySelectorAll('.u-series');
const seriesArray = Array.from(seriesEls);
seriesArray.forEach((seriesEl, index) => {

View File

@ -17,12 +17,12 @@ body {
}
.u-legend {
max-height: 30px; // slicing the height of the widget Header height ;
max-height: 30px; // Default height for backward compatibility
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 0.3rem;
width: 0.5rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
@ -53,6 +53,99 @@ body {
text-decoration-thickness: 3px;
}
}
// Enhanced legend styles
&.u-legend-enhanced {
max-height: none; // Remove default max-height restriction
padding: 6px 4px; // Back to original padding
// Thin and neat scrollbar for enhanced legend
&::-webkit-scrollbar {
width: 0.25rem;
height: 0.25rem;
}
&::-webkit-scrollbar-thumb {
background: rgba(136, 136, 136, 0.4);
border-radius: 0.125rem;
&:hover {
background: rgba(136, 136, 136, 0.7);
}
}
&::-webkit-scrollbar-track {
background: transparent;
}
// Enhanced table layout for better responsiveness
table {
width: 100%;
table-layout: fixed;
}
tbody {
display: flex;
flex-wrap: wrap;
gap: 2px 2px;
align-items: center;
justify-content: flex-start;
width: 100%;
}
// Center alignment for single-line legends
&.u-legend-single-line tbody {
justify-content: center;
}
tr.u-series {
display: flex;
flex: 0 0 auto;
min-width: fit-content;
th {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 400;
padding: 6px 10px;
cursor: pointer;
white-space: nowrap;
-webkit-font-smoothing: antialiased;
border-radius: 2px;
&:hover {
background: rgba(255, 255, 255, 0.05);
}
.u-marker {
border-radius: 50%;
min-width: 10px;
min-height: 10px;
width: 10px;
height: 10px;
flex-shrink: 0;
}
}
&.u-off {
opacity: 0.5;
text-decoration: line-through;
text-decoration-thickness: 1px;
th {
&:hover {
opacity: 0.7;
}
}
}
// Focus styles for keyboard navigation
&:focus {
outline: 1px solid rgba(66, 165, 245, 0.8);
outline-offset: 1px;
}
}
}
}
/* Style the selected background */
@ -250,6 +343,39 @@ body {
}
}
}
// Enhanced legend light mode styles
.u-legend-enhanced {
// Light mode scrollbar styling
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
&:hover {
background: rgba(0, 0, 0, 0.4);
}
}
tr.u-series {
th {
&:hover {
background: rgba(0, 0, 0, 0.05);
}
}
&.u-off {
th {
&:hover {
background: rgba(0, 0, 0, 0.08);
}
}
}
// Light mode focus styles
&:focus {
outline: 1px solid rgba(25, 118, 210, 0.8);
}
}
}
}
.ant-notification-notice-message {