mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-28 15:48:12 +00:00
feat: added enhancements to legends in panel
This commit is contained in:
parent
0925ae73a9
commit
522ee2f4a7
@ -138,6 +138,7 @@ function UplotPanelWrapper({
|
||||
timezone: timezone.value,
|
||||
customSeries,
|
||||
isLogScale: widget?.isLogScale,
|
||||
enhancedLegend: true, // Enable enhanced legend
|
||||
}),
|
||||
[
|
||||
widget?.id,
|
||||
|
||||
123
frontend/src/container/PanelWrapper/enhancedLegend.ts
Normal file
123
frontend/src/container/PanelWrapper/enhancedLegend.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
@ -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) => {
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user