Skip to content

Commit bf3243c

Browse files
fix(charts): reserve the authored bar gap between grouped bars
Grouped horizontal bar rows now size their gap from the series `barGap` the way ECharts does (pixels, a percentage of bar width, default 20%, overlap for negative gaps) instead of a fixed 4px. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S6aTRnkiu7PxYZPNPXYEMV
1 parent af21484 commit bf3243c

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

‎apps/sim/lib/charts/option.test.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,23 @@ describe('horizontal bar label layout', () => {
4646
expect(authored.yAxis[0]).not.toHaveProperty('axisLabel')
4747
})
4848

49+
it('reserves the authored gap between grouped bars in each row', () => {
50+
const grouped = (barGap?: string | number) => ({
51+
xAxis: { type: 'value' },
52+
yAxis: { type: 'category' },
53+
series: [
54+
{ type: 'bar', barWidth: 20, ...(barGap === undefined ? {} : { barGap }) },
55+
{ type: 'bar', barWidth: 20 },
56+
],
57+
})
58+
const rows = 10
59+
const base = horizontalBarChartHeight(grouped('0%'), rows) ?? 0
60+
expect(horizontalBarChartHeight(grouped(), rows)).toBe(base + rows * 4)
61+
expect(horizontalBarChartHeight(grouped('150%'), rows)).toBe(base + rows * 30)
62+
expect(horizontalBarChartHeight(grouped(40), rows)).toBe(base + rows * 40)
63+
expect(horizontalBarChartHeight(grouped('-100%'), rows)).toBe(base - rows * 20)
64+
})
65+
4966
it('keeps every authored tooltip entry when turning off the shadow pointer', () => {
5067
const result = buildChartRenderOption({
5168
option: {

‎apps/sim/lib/charts/option.ts‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,29 @@ export function mapTooltipEntries(
4646

4747
const CATEGORY_LABEL_LAYOUT_KEYS = ['inside', 'width', 'margin'] as const
4848

49-
const BAR_GROUP_GAP = 4
49+
/** ECharts' default `barGap`: the space between grouped bars, relative to bar width. */
50+
const DEFAULT_BAR_GAP = '20%'
51+
52+
/**
53+
* Pixel gap between side-by-side bars. ECharts reads `barGap` from the last series that sets
54+
* it: a number is pixels, a percentage is relative to the bar width, and a negative gap
55+
* overlaps the bars, which then need no extra space.
56+
*/
57+
function barGapPixels(option: Record<string, unknown>, barWidth: number): number {
58+
const series = Array.isArray(option.series) ? option.series : [option.series]
59+
let gap: unknown = DEFAULT_BAR_GAP
60+
for (const entry of series) {
61+
const barGap = toRecord(entry).barGap
62+
if (barGap !== undefined) gap = barGap
63+
}
64+
const pixels =
65+
typeof gap === 'number'
66+
? gap
67+
: typeof gap === 'string' && gap.endsWith('%')
68+
? (barWidth * Number.parseFloat(gap)) / 100
69+
: Number(gap)
70+
return Number.isFinite(pixels) ? pixels : 0
71+
}
5072

5173
/**
5274
* Bar thickness per slot in one category row: stacked series share a slot, every other series
@@ -212,8 +234,11 @@ export function horizontalBarChartHeight(
212234
): number | null {
213235
if (!isHorizontalBarOption(option)) return null
214236
const slots = barSlotWidths(option)
215-
const barsHeight =
216-
slots.reduce((total, width) => total + width, 0) + BAR_GROUP_GAP * (slots.length - 1)
237+
const gap = barGapPixels(option, Math.max(...slots))
238+
const barsHeight = Math.max(
239+
Math.max(...slots),
240+
slots.reduce((total, width) => total + width, 0) + gap * (slots.length - 1)
241+
)
217242
const rowHeight =
218243
barsHeight + (authorsCategoryLabelColumn(option) ? LEFT_LABEL_ROW_GAP : ABOVE_BAR_LABEL_SPACE)
219244
return Math.max(MIN_CHART_HEIGHT, HORIZONTAL_BAR_CHROME_HEIGHT + rowCount * rowHeight)

0 commit comments

Comments
 (0)