Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(label): support labels auto-rotation #19348

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

agurtovoy
Copy link

@agurtovoy agurtovoy commented Nov 30, 2023

Brief Information

This pull request is in the type of:

  • bug fixing
  • new feature
  • others

What does this PR do?

Adds an option to auto-rotate category labels based on available space, specified category interval, and the list of provided rotations.

Fixed issues

Details

Before: What was the problem?

The axis label rotation option enhances usability of charts with a large number of categories and/or long category names by sparing the user the effort of hovering over specific value just to see its corresponding category; the usability enhancement is even greater for non-interactive, statically rendered charts. The rotation option currently has to be chosen beforehand, though, which is suboptimal when a chart is populated with dynamic data -- depending on the data, the labels might or might need to be rotated, and the optimal rotation angle might vary.

After: How does it behave after the fixing?

This PR introduces a new axisLabel.autoRotate: boolean | number[] option, which works in concert with the axisLabel.interval: 'auto' and grid.containLabel: true options to automatically manage (category) label rotation, interval, and layout:

Kapture.2023-11-30.at.10.55.37.mp4
Kapture.2023-11-30.at.10.58.31.mp4
Kapture.2023-11-30.at.11.00.44.mp4

Document Info

  • This PR doesn't relate to document changes
  • The document should be updated later
  • The document changes have been made in apache/echarts-doc#xxx

I'll update the documentation once the API is settled.

Misc

Related test cases or examples to use the new APIs

See the new axis-labelAutoRotate.html test cases.

Others

Merging options

  • Please squash the commits into a single one when merging.

Copy link

echarts-bot bot commented Nov 30, 2023

Thanks for your contribution!
The community will review it ASAP. In the meanwhile, please checkout the coding standard and Wiki about How to make a pull request.

Document changes are required in this PR. Please also make a PR to apache/echarts-doc for document changes and update the issue id in the PR description. When the doc PR is merged, the maintainers will remove the PR: awaiting doc label.

@@ -599,11 +599,6 @@ function isTwoLabelOverlapped(
return firstRect.intersect(nextRect);
}

function isNameLocationCenter(nameLocation: string) {
return nameLocation === 'middle' || nameLocation === 'center';
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to coord/axisHelper so it can be reused elsewhere.

@@ -740,13 +735,10 @@ function buildAxisLabel(

const labelModel = axisModel.getModel('axisLabel');
const labelMargin = labelModel.get('margin');
const labels = axis.getViewLabels();
const { labels, rotation } = axis.getViewLabelsAndRotation();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Labels + calculated rotation.

}

getNameGap(): ReturnType<typeof getAxisNameGap> {
return getAxisNameGap(this);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As correctly pointed out in #14996 (comment), the utility of the auto-rotate feature would be severely diminished unless we also gave users a way to automatically manage the location of the centered axis names. This is accomplished through the introduction of the nameLayout: 'auto' option -- please see below.

nameGap?: number;
nameLayout?: 'auto';
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative was adding 'auto' as one of the nameGap's allowed values, but that would take away the ability to control the automatically managed distance between the name and the labels.

@agurtovoy agurtovoy force-pushed the fix-14996-feat-axis-label-auto-rotate branch from e35cb75 to a75ed79 Compare November 30, 2023 17:24
@@ -219,6 +220,8 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
// Whether axisLabel is inside the grid or outside the grid.
inside?: boolean,
rotate?: number,
autoRotate?: boolean | [number, ...number[]],
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An attempted alternative was allowing the auto and number[] options for axisLabel.rotate, but that created too many rippling changes through the codebase. The implemented behavior is that specifying "hard-coded" label rotation option through rotate disables autorotation.

@@ -219,6 +220,8 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
// Whether axisLabel is inside the grid or outside the grid.
inside?: boolean,
rotate?: number,
autoRotate?: boolean | [number, ...number[]],
minDistance?: number,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minDistance is the minimal-allowed distance between the labels before they are rotated/collapsed.

@@ -232,7 +235,11 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
// Color can be callback
color?: ColorString | ((value?: string | number, index?: number) => ColorString)
overflow?: TextStyleProps['overflow']
// approximate auto-layout computations (autoRotate, hideOverlap) if the total number of axis labels is over
// the trheshold; defaults to 40
layoutApproximationThreshold?: number
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was previously hard-coded in the code. Elevated it into an option so that users can choose when/whether they want to trigger this optimization, since it could lead to less-than-precise layout.

@@ -83,6 +83,7 @@ const defaultOption: AxisBaseOption = {
// Whether axisLabel is inside the grid or outside the grid.
inside: false,
rotate: 0,
minDistance: 10,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default labels' minDistance to 10px to match the current observable auto-interval behavior.

* @param axis
* @return Be null/undefined if no labels.
*/
export function estimateLabelUnionRect(axis: Axis) {
Copy link
Author

@agurtovoy agurtovoy Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to axisTickLabelBuilder.ts (the only place this was called from) so we can reuse label size computations we do there.

@@ -399,3 +335,7 @@ export function unionAxisExtentFromData(dataExtent: number[], data: SeriesData,
});
}
}

export function isNameLocationCenter(nameLocation: string) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved here from AxisBuilder.ts for cleaner reuse.

@agurtovoy agurtovoy force-pushed the fix-14996-feat-axis-label-auto-rotate branch from a75ed79 to eb35c0b Compare November 30, 2023 19:18
if (axesModel.get('nameLayout') === 'auto'
&& isNameLocationCenter(axis.model.get('nameLocation'))
) {
const labelUnionRect = estimateLabelUnionRect(axis);
Copy link
Author

@agurtovoy agurtovoy Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For centered labels with nameLayout: 'auto', calculate the name gap dynamically to "stack" the axis name together with the labels:

top axis bottom axis left axis right axis

y: bboxOffset * cos
};

return { axesIntersection, bounds, offset };
Copy link
Author

@agurtovoy agurtovoy Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://codepen.io/agurtovoy/pen/WNPyqWx for the visualization of the math here.

label rotation visualization

);
// Magic number
width = rect.width * 1.3;
height = rect.height * 1.3;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic 1.3 coefficient here was effectively a workaround for a subtle bug that caused an incorrect axis pixel extent to be used when calculating the category axis' unit dimensions. Please see below for the bug fix.

adjustAxes();
// sort axes to make sure we estimate ordinal axes' label dimensions after we adjusted
// the available grid space based on the value axes' label dimensions
const axesList = [...this._axesList].sort((a, b) => axisOrder(a) - axisOrder(b));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part 1 of the "magic coefficient" bug fix referenced earlier...


// adjust axes after each potential change to grid dimensions
// so that the next (ordinal) axis' label estimations are more precise
adjustAxes();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... part 2 of the "magic coefficient" bug fix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for the new functionality.

Copy link
Contributor

github-actions bot commented Dec 1, 2023

The changes brought by this PR can be previewed at: https://echarts.apache.org/examples/editor?version=PR-19348@fcd1590

@agurtovoy agurtovoy force-pushed the fix-14996-feat-axis-label-auto-rotate branch 2 times, most recently from 627ad46 to a2a6008 Compare December 14, 2023 19:48
@agurtovoy agurtovoy force-pushed the fix-14996-feat-axis-label-auto-rotate branch from a2a6008 to fcd1590 Compare January 16, 2024 21:52
@agurtovoy
Copy link
Author

@Ovilia @plainheart What can I do to help you to review this? Would love to get this in soon.

@dobropeeriq
Copy link

I'd love to begin using this feature. Please review.

@Splinteer
Copy link

Thank you for your work @agurtovoy.
I was waiting this feature for so long, it was one of the most negative points of echarts

@Ovilia Ovilia added this to the 6.0.0 milestone Mar 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants