feat(charts): enhance TauriBarChart with overflow handling for dense months
This commit is contained in:
@@ -34,6 +34,31 @@ describe('TauriBarChart', () => {
|
|||||||
expect(container.querySelectorAll('.bg-\\[\\#5559CE\\]')).toHaveLength(31);
|
expect(container.querySelectorAll('.bg-\\[\\#5559CE\\]')).toHaveLength(31);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fixed-width bar makes its column's intrinsic min-width 24px, so a 31-day
|
||||||
|
* month sums past the plot width and the row overflows out of the card
|
||||||
|
* (bars land on the neighbouring chart). Columns must be shrinkable and the
|
||||||
|
* bar fluid-but-capped.
|
||||||
|
*/
|
||||||
|
it('keeps a dense month inside the card: shrinkable columns, fluid bars', () => {
|
||||||
|
const month: ChartPoint[] = Array.from({ length: 31 }, (_, i) => ({ label: String(i + 1), value: i % 5 }));
|
||||||
|
const { container } = render(<TauriBarChart data={month} />);
|
||||||
|
|
||||||
|
const bar = container.querySelector('.bg-\\[\\#5559CE\\]') as HTMLElement;
|
||||||
|
// کسری از ستون، نه تمام آن — وگرنه میلهها بدون فاصله به هم میچسبند
|
||||||
|
expect(bar.className).toMatch(/\bw-\[\d+%\]/);
|
||||||
|
expect(bar.className.split(/\s+/)).not.toContain('w-full');
|
||||||
|
expect(bar.className).toContain('max-w-[24px]');
|
||||||
|
// فقط سقف عرض، نه عرض ثابت (max-w-[24px] خودش شامل رشتهی w-[24px] است)
|
||||||
|
expect(bar.className.split(/\s+/)).not.toContain('w-[24px]');
|
||||||
|
|
||||||
|
const column = bar.parentElement as HTMLElement;
|
||||||
|
expect(column.className).toContain('min-w-0');
|
||||||
|
|
||||||
|
const plot = container.querySelector('.relative.flex-1') as HTMLElement;
|
||||||
|
expect(plot.className).toContain('overflow-hidden');
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* styles.css ships an unlayered legacy `.flex { align-items: center }` that
|
* styles.css ships an unlayered legacy `.flex { align-items: center }` that
|
||||||
* beats Tailwind's `items-*` utilities and collapses the plot column to zero
|
* beats Tailwind's `items-*` utilities and collapses the plot column to zero
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ function ChartFrame({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* plot area */}
|
{/* plot area */}
|
||||||
<div className="relative flex-1 min-w-0">
|
{/* overflow-hidden keeps a dense series (a 31-day month) inside the card */}
|
||||||
|
<div className="relative flex-1 min-w-0 overflow-hidden">
|
||||||
{ticks.map((_, i) => (
|
{ticks.map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
@@ -79,7 +80,7 @@ function ChartFrame({
|
|||||||
{/* x-axis labels */}
|
{/* x-axis labels */}
|
||||||
<div className="flex pt-[8px]" style={{ paddingRight: yWidth, alignItems: 'flex-start' }}>
|
<div className="flex pt-[8px]" style={{ paddingRight: yWidth, alignItems: 'flex-start' }}>
|
||||||
{labels.map((l, i) => (
|
{labels.map((l, i) => (
|
||||||
<span key={i} className="flex-1 text-center text-[10px] font-normal text-[#7E7E7E] whitespace-nowrap">
|
<span key={i} className="flex-1 min-w-0 overflow-hidden text-center text-[10px] font-normal text-[#7E7E7E] whitespace-nowrap">
|
||||||
{l}
|
{l}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -98,12 +99,17 @@ export function TauriBarChart({ data }: { data: ChartPoint[] }) {
|
|||||||
const top = ticks[0] || 1;
|
const top = ticks[0] || 1;
|
||||||
return (
|
return (
|
||||||
<ChartFrame ticks={ticks} labels={data.map((d) => d.label)} yWidth={42}>
|
<ChartFrame ticks={ticks} labels={data.map((d) => d.label)} yWidth={42}>
|
||||||
|
{/* `min-w-0` on every column is required: without it each column's
|
||||||
|
intrinsic min-width is the bar's own width, so a 31-day month sums to
|
||||||
|
more than the plot width and the whole row overflows out of the card.
|
||||||
|
The bar is therefore a fraction of its column (which leaves the gap
|
||||||
|
between bars) and only capped at the source's 24px. */}
|
||||||
<div className="absolute inset-0 flex items-stretch" style={{ alignItems: 'stretch' }}>
|
<div className="absolute inset-0 flex items-stretch" style={{ alignItems: 'stretch' }}>
|
||||||
{data.map((d, i) => (
|
{data.map((d, i) => (
|
||||||
<div key={i} className="flex-1 flex items-end justify-center" style={{ alignItems: 'flex-end' }}>
|
<div key={i} className="flex-1 min-w-0 flex items-end justify-center" style={{ alignItems: 'flex-end' }}>
|
||||||
<div
|
<div
|
||||||
title={faNum.format(d.value)}
|
title={faNum.format(d.value)}
|
||||||
className="w-[24px] max-w-[30%] rounded-t-[3px] bg-[#5559CE]"
|
className="w-[60%] max-w-[24px] rounded-t-[3px] bg-[#5559CE]"
|
||||||
style={{
|
style={{
|
||||||
height: `${(d.value / top) * 100}%`,
|
height: `${(d.value / top) * 100}%`,
|
||||||
minHeight: d.value > 0 ? 4 : 0,
|
minHeight: d.value > 0 ? 4 : 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user