Skip to content

Commit abd13df

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

File tree

3 files changed

+389
-13
lines changed

3 files changed

+389
-13
lines changed

src/label/LabelManager.ts

Lines changed: 147 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,129 @@ 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+
// Note: api.updateLabelLayout() will be called externally
248+
}
249+
250+
private reorderAndHideOverlap = throttle(() => {
251+
if (this.labelsNeedsHideOverlap.length === 0) {
252+
return;
253+
}
254+
255+
// Create a copy for reordering
256+
const reorderedLabels = [...this.labelsNeedsHideOverlap];
257+
258+
// Sort: emphasis labels first, then by original priority
259+
reorderedLabels.sort((a, b) => {
260+
const aIsEmphasis = this.currentEmphasisLabels.has(a.label) ? 1 : 0;
261+
const bIsEmphasis = this.currentEmphasisLabels.has(b.label) ? 1 : 0;
262+
263+
// Emphasis labels come first
264+
if (aIsEmphasis !== bIsEmphasis) {
265+
return bIsEmphasis - aIsEmphasis;
266+
}
267+
268+
// Then by original priority
269+
return ((b.suggestIgnore ? 1 : 0) - (a.suggestIgnore ? 1 : 0))
270+
|| (b.priority - a.priority);
271+
});
272+
273+
// First restore all to show state
274+
reorderedLabels.forEach(item => {
275+
item.label.ignore = false;
276+
const emphasisState = item.label.ensureState('emphasis');
277+
emphasisState.ignore = false;
278+
279+
if (item.labelLine) {
280+
item.labelLine.ignore = false;
281+
const lineEmphasisState = item.labelLine.ensureState('emphasis');
282+
lineEmphasisState.ignore = false;
283+
}
284+
});
285+
286+
// Call hideOverlap with isOrdered = true
287+
hideOverlap(reorderedLabels, true);
288+
}, 16, true);
289+
290+
private restoreOriginalState = throttle(() => {
291+
this.labelsNeedsHideOverlap.forEach(item => {
292+
const originalIgnore = this.originalStates.get(item.label) ?? false;
293+
item.label.ignore = originalIgnore;
294+
295+
// For emphasis state, use the original hideOverlap logic
296+
const emphasisState = item.label.ensureState('emphasis');
297+
emphasisState.ignore = originalIgnore;
298+
299+
if (item.labelLine) {
300+
const originalLineIgnore = this.originalStates.get(item.labelLine) ?? false;
301+
item.labelLine.ignore = originalLineIgnore;
302+
303+
const lineEmphasisState = item.labelLine.ensureState('emphasis');
304+
lineEmphasisState.ignore = originalLineIgnore;
305+
}
306+
});
307+
}, 16, true);
308+
309+
clear(): void {
310+
this.currentEmphasisLabels.clear();
311+
this.labelsNeedsHideOverlap = [];
312+
this.originalStates.clear();
313+
}
314+
}
315+
197316
class LabelManager {
198317

199318
private _labelList: LabelDesc[] = [];
200319
private _chartViewList: ChartView[] = [];
320+
private _emphasisManager: EmphasisManager = new EmphasisManager();
201321

202322
constructor() {}
203323

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

467613
restoreIgnore(labelsNeedsHideOverlap);
468614
hideOverlap(labelsNeedsHideOverlap);
615+
this._emphasisManager.setLabelsNeedsHideOverlap(labelsNeedsHideOverlap);
469616
}
470617

471618
/**

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)