Skip to content

Commit 3a924cd

Browse files
feat: improve label stacking to reuse lanes and prevent overlaps
Previously, stack indices were assigned by simple incrementing counter, which caused unnecessary gaps in the label stacks. Now, the algorithm checks for available lanes and reuses them when possible.
1 parent 47f69c4 commit 3a924cd

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

src/component/utility/stackOverlappingLabels.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,28 +38,46 @@ export function stackOverlappingLabelsArray<T extends Record<string, any>>(
3838
});
3939
});
4040
}
41-
41+
/**
42+
* Assigns a stack index (vertical lane) to each item so that horizontally
43+
* overlapping labels are pushed to different lanes, while non-overlapping
44+
* labels reuse the lowest available lane.
45+
* */
4246
export function stackOverlappingLabelsMap<T extends Record<string, any>>(
4347
items: T[],
4448
options: StackOverlappingLabelsOptions<T> & { idKey: keyof T },
4549
): StackOverlappingLabelsMapReturnType {
46-
const { idKey } = options;
50+
const { idKey, startPositionKey, labelWidthKey, padding = 0 } = options;
4751
const groups = stackOverlappingLabels(items, options);
4852

4953
const stackMap: StackOverlappingLabelsMapReturnType = {};
54+
5055
for (const group of groups) {
51-
let i = 0;
56+
const laneEdges: number[] = [];
57+
5258
for (const item of group) {
5359
const key = item[idKey];
5460
if (key === undefined || key === null) {
5561
throw new Error(
5662
`Invalid or missing idKey value for item: ${JSON.stringify(item)}`,
5763
);
5864
}
59-
stackMap[key as string | number] = i;
60-
if (item.assignment) i++;
65+
66+
const startPosition = item[startPositionKey];
67+
const itemEnd = startPosition + item[labelWidthKey] + padding;
68+
69+
// Find the first lane whose right edge does not overlap this item is start.
70+
// If no such lane exists, open a new one.
71+
let assignedLane = laneEdges.findIndex((edge) => edge <= startPosition);
72+
if (assignedLane === -1) {
73+
assignedLane = laneEdges.length;
74+
}
75+
76+
laneEdges[assignedLane] = itemEnd;
77+
stackMap[key] = assignedLane;
6178
}
6279
}
80+
6381
return stackMap;
6482
}
6583

@@ -84,7 +102,7 @@ function stackOverlappingLabels<T extends Record<string, any>>(
84102
}
85103
currentGroup = [item];
86104
}
87-
lastInPixel = startPosition + labelWidth + padding;
105+
lastInPixel = Math.max(lastInPixel, startPosition + labelWidth + padding);
88106
}
89107

90108
if (currentGroup.length > 0) {

0 commit comments

Comments
 (0)