mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-03 08:33:26 +00:00
* feat: added chartlayout component to render charts and legends * fix: added fix for legenditemsSet calculation * chore: added pulse frontend as codeowners for uplotv2 * chore: cleaned up the legend size calculations function * chore: removed config from deps in charlayout * fix: added fix for height calculation * fix: pr review changes
132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
import { LegendConfig, LegendPosition } from 'lib/uPlotV2/components/types';
|
|
|
|
export interface ChartDimensions {
|
|
width: number;
|
|
height: number;
|
|
legendWidth: number;
|
|
legendHeight: number;
|
|
legendsPerSet: number;
|
|
}
|
|
|
|
const AVG_CHAR_WIDTH = 8;
|
|
const DEFAULT_AVG_LABEL_LENGTH = 15;
|
|
const LEGEND_GAP = 16;
|
|
const LEGEND_PADDING = 12;
|
|
const LEGEND_LINE_HEIGHT = 36;
|
|
const MAX_LEGEND_WIDTH = 400;
|
|
|
|
/**
|
|
* Average text width from series labels (for legendsPerSet).
|
|
*/
|
|
export function calculateAverageLegendWidth(legends: string[]): number {
|
|
if (legends.length === 0) {
|
|
return DEFAULT_AVG_LABEL_LENGTH;
|
|
}
|
|
const averageLabelLength =
|
|
legends.reduce((sum, l) => sum + l.length, 0) / legends.length;
|
|
return averageLabelLength * AVG_CHAR_WIDTH;
|
|
}
|
|
|
|
/**
|
|
* Compute how much space to give to the chart area vs. the legend.
|
|
*
|
|
* - For a RIGHT legend, we reserve a vertical column on the right and shrink the chart width.
|
|
* - For a BOTTOM legend, we reserve up to two rows below the chart and shrink the chart height.
|
|
*
|
|
* Implementation details (high level):
|
|
* - Approximates legend item width from label text length, using a fixed average char width.
|
|
* - RIGHT legend:
|
|
* - `legendWidth` is clamped between 150px and min(MAX_LEGEND_WIDTH, 30% of container width).
|
|
* - Chart width is `containerWidth - legendWidth`.
|
|
* - BOTTOM legend:
|
|
* - Computes how many items fit per row, then uses at most 2 rows.
|
|
* - `legendHeight` is derived from row count, capped by both a fixed pixel max and a % of container height.
|
|
* - Chart height is `containerHeight - legendHeight`, never below 0.
|
|
* - `legendsPerSet` is the number of legend items that fit horizontally, based on the same text-width approximation.
|
|
*
|
|
* The returned values are the final chart and legend rectangles (width/height),
|
|
* plus `legendsPerSet` which hints how many legend items to show per row.
|
|
*/
|
|
export function calculateChartDimensions({
|
|
containerWidth,
|
|
containerHeight,
|
|
legendConfig,
|
|
seriesLabels,
|
|
}: {
|
|
containerWidth: number;
|
|
containerHeight: number;
|
|
legendConfig: LegendConfig;
|
|
seriesLabels: string[];
|
|
}): ChartDimensions {
|
|
// Guard: no space to lay out chart or legend
|
|
if (containerWidth <= 0 || containerHeight <= 0) {
|
|
return {
|
|
width: 0,
|
|
height: 0,
|
|
legendWidth: 0,
|
|
legendHeight: 0,
|
|
legendsPerSet: 0,
|
|
};
|
|
}
|
|
|
|
// Approximate width of a single legend item based on label text.
|
|
const approxLegendItemWidth = calculateAverageLegendWidth(seriesLabels);
|
|
const legendItemCount = seriesLabels.length;
|
|
|
|
if (legendConfig.position === LegendPosition.RIGHT) {
|
|
const maxRightLegendWidth = Math.min(MAX_LEGEND_WIDTH, containerWidth * 0.3);
|
|
const rightLegendWidth = Math.min(
|
|
Math.max(150, approxLegendItemWidth),
|
|
maxRightLegendWidth,
|
|
);
|
|
|
|
return {
|
|
width: Math.max(0, containerWidth - rightLegendWidth),
|
|
height: containerHeight,
|
|
legendWidth: rightLegendWidth,
|
|
legendHeight: containerHeight,
|
|
// Single vertical list on the right.
|
|
legendsPerSet: 1,
|
|
};
|
|
}
|
|
|
|
const legendRowHeight = LEGEND_LINE_HEIGHT + LEGEND_PADDING;
|
|
|
|
const legendItemWidth = Math.min(approxLegendItemWidth, 400);
|
|
const legendItemsPerRow = Math.max(
|
|
1,
|
|
Math.floor((containerWidth - LEGEND_PADDING * 2) / legendItemWidth),
|
|
);
|
|
|
|
const legendRowCount = Math.min(
|
|
2,
|
|
Math.ceil(legendItemCount / legendItemsPerRow),
|
|
);
|
|
|
|
const idealBottomLegendHeight =
|
|
legendRowCount > 1
|
|
? legendRowCount * legendRowHeight - LEGEND_PADDING
|
|
: legendRowHeight;
|
|
|
|
const maxAllowedLegendHeight = Math.min(2 * legendRowHeight, 80);
|
|
|
|
const bottomLegendHeight = Math.min(
|
|
idealBottomLegendHeight,
|
|
maxAllowedLegendHeight,
|
|
);
|
|
|
|
// How many legend items per row in the Legend component.
|
|
const legendsPerSet = Math.ceil(
|
|
(containerWidth + LEGEND_GAP) /
|
|
(Math.min(MAX_LEGEND_WIDTH, approxLegendItemWidth) + LEGEND_GAP),
|
|
);
|
|
|
|
return {
|
|
width: containerWidth,
|
|
height: Math.max(0, containerHeight - bottomLegendHeight),
|
|
legendWidth: containerWidth,
|
|
legendHeight: bottomLegendHeight,
|
|
legendsPerSet,
|
|
};
|
|
}
|