Skip to content

Commit 88eb99f

Browse files
committed
fix(datalabel): hideOverlap not work on emphasis state
1 parent 8ba89a9 commit 88eb99f

File tree

3 files changed

+387
-13
lines changed

3 files changed

+387
-13
lines changed

src/label/LabelManager.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
} from './labelLayoutHelper';
6060
import { labelInner, animateLabelValue } from './labelStyle';
6161
import { normalizeRadian } from 'zrender/src/contain/util';
62+
import { throttle } from '../util/throttle';
6263

6364
interface LabelDesc {
6465
label: ZRText
@@ -194,10 +195,127 @@ function extendWithKeys(target: Dictionary<any>, source: Dictionary<any>, keys:
194195

195196
const LABEL_LAYOUT_PROPS = ['x', 'y', 'rotation'];
196197

198+
/**
199+
* Emphasis manager for handling label emphasis state changes
200+
*/
201+
class EmphasisManager {
202+
// eslint-disable-next-line no-undef
203+
private currentEmphasisLabels: Set<Element> = new Set();
204+
private labelsNeedsHideOverlap: LabelLayoutWithGeometry[] = [];
205+
// eslint-disable-next-line no-undef
206+
private originalStates: Map<Element, boolean> = new Map();
207+
208+
setLabelsNeedsHideOverlap(labels: LabelLayoutWithGeometry[]): void {
209+
this.clear();
210+
if (labels.length === 0) {
211+
return;
212+
}
213+
214+
this.labelsNeedsHideOverlap = labels;
215+
216+
// Record original ignore states only when needed
217+
labels.forEach(item => {
218+
this.originalStates.set(item.label, item.label.ignore);
219+
if (item.labelLine) {
220+
this.originalStates.set(item.labelLine, item.labelLine.ignore);
221+
}
222+
});
223+
}
224+
225+
handleEmphasisChange(targetLabel: Element, isEnteringEmphasis: boolean): void {
226+
// Early return if no labels need hideOverlap processing
227+
if (this.labelsNeedsHideOverlap.length === 0) {
228+
return;
229+
}
230+
231+
if (isEnteringEmphasis) {
232+
this.currentEmphasisLabels.add(targetLabel);
233+
}
234+
else {
235+
this.currentEmphasisLabels.delete(targetLabel);
236+
}
237+
238+
if (this.currentEmphasisLabels.size === 0) {
239+
// No emphasis labels, restore original state
240+
this.restoreOriginalState();
241+
}
242+
else {
243+
// Re-sort with emphasis labels first and call hideOverlap
244+
this.reorderAndHideOverlap();
245+
}
246+
}
247+
248+
private reorderAndHideOverlap = throttle(() => {
249+
if (this.labelsNeedsHideOverlap.length === 0) {
250+
return;
251+
}
252+
253+
// Create a copy for reordering
254+
const reorderedLabels = [...this.labelsNeedsHideOverlap];
255+
256+
// Sort: emphasis labels first, then by original priority
257+
reorderedLabels.sort((a, b) => {
258+
const aIsEmphasis = this.currentEmphasisLabels.has(a.label) ? 1 : 0;
259+
const bIsEmphasis = this.currentEmphasisLabels.has(b.label) ? 1 : 0;
260+
261+
// Emphasis labels come first
262+
if (aIsEmphasis !== bIsEmphasis) {
263+
return bIsEmphasis - aIsEmphasis;
264+
}
265+
266+
// Then by original priority
267+
return ((b.suggestIgnore ? 1 : 0) - (a.suggestIgnore ? 1 : 0))
268+
|| (b.priority - a.priority);
269+
});
270+
271+
// First restore all to show state
272+
reorderedLabels.forEach(item => {
273+
item.label.ignore = false;
274+
const emphasisState = item.label.ensureState('emphasis');
275+
emphasisState.ignore = false;
276+
277+
if (item.labelLine) {
278+
item.labelLine.ignore = false;
279+
const lineEmphasisState = item.labelLine.ensureState('emphasis');
280+
lineEmphasisState.ignore = false;
281+
}
282+
});
283+
284+
// Call hideOverlap with isOrdered = true
285+
hideOverlap(reorderedLabels, true);
286+
}, 16, true);
287+
288+
private restoreOriginalState = throttle(() => {
289+
this.labelsNeedsHideOverlap.forEach(item => {
290+
const originalIgnore = this.originalStates.get(item.label) ?? false;
291+
item.label.ignore = originalIgnore;
292+
293+
// For emphasis state, use the original hideOverlap logic
294+
const emphasisState = item.label.ensureState('emphasis');
295+
emphasisState.ignore = originalIgnore;
296+
297+
if (item.labelLine) {
298+
const originalLineIgnore = this.originalStates.get(item.labelLine) ?? false;
299+
item.labelLine.ignore = originalLineIgnore;
300+
301+
const lineEmphasisState = item.labelLine.ensureState('emphasis');
302+
lineEmphasisState.ignore = originalLineIgnore;
303+
}
304+
});
305+
}, 16, true);
306+
307+
clear(): void {
308+
this.currentEmphasisLabels.clear();
309+
this.labelsNeedsHideOverlap = [];
310+
this.originalStates.clear();
311+
}
312+
}
313+
197314
class LabelManager {
198315

199316
private _labelList: LabelDesc[] = [];
200317
private _chartViewList: ChartView[] = [];
318+
private _emphasisManager: EmphasisManager = new EmphasisManager();
201319

202320
constructor() {}
203321

@@ -323,6 +441,32 @@ class LabelManager {
323441
// Can only attach the text on the element with dataIndex
324442
if (textEl && !(textEl as ECElement).disableLabelLayout) {
325443
this._addLabel(ecData.dataIndex, ecData.dataType, seriesModel, textEl, layoutOption);
444+
// Add emphasis state change listener for hideOverlap labels
445+
const resolvedLayoutOption = isFunction(layoutOption) ? null : layoutOption;
446+
if (resolvedLayoutOption && resolvedLayoutOption.hideOverlap) {
447+
const hostEl = child as ECElement;
448+
const originalOnHoverStateChange = hostEl.onHoverStateChange;
449+
const labelManager = this;
450+
451+
hostEl.onHoverStateChange = function (toState: string) {
452+
// Call original handler first
453+
if (originalOnHoverStateChange) {
454+
originalOnHoverStateChange.call(this, toState);
455+
}
456+
457+
// Handle emphasis state change for hideOverlap labels
458+
if (toState === 'emphasis' || toState === 'normal') {
459+
// Find the label element - could be textEl or child itself
460+
const labelElement = textEl || this;
461+
462+
// Use EmphasisManager to handle the state change
463+
labelManager._emphasisManager.handleEmphasisChange(
464+
labelElement,
465+
toState === 'emphasis'
466+
);
467+
}
468+
};
469+
}
326470
}
327471
});
328472
}
@@ -466,6 +610,7 @@ class LabelManager {
466610

467611
restoreIgnore(labelsNeedsHideOverlap);
468612
hideOverlap(labelsNeedsHideOverlap);
613+
this._emphasisManager.setLabelsNeedsHideOverlap(labelsNeedsHideOverlap);
469614
}
470615

471616
/**

src/label/labelLayoutHelper.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -519,25 +519,22 @@ export function restoreIgnore(labelList: LabelLayoutData[]): void {
519519
* PENDING: although currently this method is effectively called in other states in `updateLabelLayout` case,
520520
* the bad case is not noticeable in the zooming scenario.
521521
*/
522-
export function hideOverlap(labelList: LabelLayoutData[]): void {
522+
export function hideOverlap(labelList: LabelLayoutData[], isOrdered?: boolean): void {
523523
const displayedLabels: LabelLayoutWithGeometry[] = [];
524524

525525
// TODO, render overflow visible first, put in the displayedLabels.
526-
labelList.sort(function (a, b) {
527-
return ((b.suggestIgnore ? 1 : 0) - (a.suggestIgnore ? 1 : 0))
528-
|| (b.priority - a.priority);
529-
});
526+
if (!isOrdered) {
527+
labelList.sort(function (a, b) {
528+
return ((b.suggestIgnore ? 1 : 0) - (a.suggestIgnore ? 1 : 0))
529+
|| (b.priority - a.priority);
530+
});
531+
}
530532

531533
function hideEl(el: Element) {
532-
if (!el.ignore) {
533-
// Show on emphasis.
534-
const emphasisState = el.ensureState('emphasis');
535-
if (emphasisState.ignore == null) {
536-
emphasisState.ignore = false;
537-
}
538-
}
539-
540534
el.ignore = true;
535+
// Also hide in emphasis state
536+
const emphasisState = el.ensureState('emphasis');
537+
emphasisState.ignore = true;
541538
}
542539

543540
for (let i = 0; i < labelList.length; i++) {

0 commit comments

Comments
 (0)