Skip to content

feat: Improves waterfall chart #33146

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

anantaoutlook
Copy link

@anantaoutlook anantaoutlook commented Apr 16, 2025

SUMMARY

This PR includes the following improvements:

1.) Horizontal waterfall in addition to vertical.
image

2.) Axis label fixes to word-wrap longer labels instead of hiding them. This is extremely important for readability/usability.
For example. Prior to these fixes, roughly half of the following labels in this chart were hidden:
image

3.) Sorting of Waterfall: It is now possible to sort the waterfall by a field other than the field indicated in the waterfall column headers. Again, extremely important. As an example, in the following chart, we are bridging from gross revenue to Profit. It's vital that these grouping appear in the correct order:
image
image

4.) Added option to use first value as subtotal, and bold first value as subtotal. These are important optional settings for the waterfall. In the example, these allow the Gross Revenue bar to be presented as a subtotal (full bar and grey), and the label for Gross Revenue is bold.
Before:
image
After:
image

5.) Cross-filtering is now feasible for the waterfall chart.

TESTING INSTRUCTIONS

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

Copy link

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

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

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Readability Suspicious file suffix in types import ▹ view ✅ Fix detected
Readability Remove or implement commented validators ▹ view 🧠 Incorrect
Functionality Order Direction Control Not Dependent on Column Selection ▹ view 🧠 Incorrect
Readability Deeply nested complex function ▹ view 🧠 Incorrect
Readability Scattered series transformation logic ▹ view 🧠 Not in scope
Functionality Inconsistent Mixed-Type Data Sorting ▹ view 🧠 Incorrect
Functionality Inaccurate Text Wrapping Calculation ▹ view 🧠 Not in scope
Readability Type inconsistency in default value ▹ view 🧠 Not in standard
Documentation Missing documentation for complex click handler ▹ view
Error Handling Inadequate Error Handling for Invalid Label Distance ▹ view 🧠 Incorrect
Files scanned
File Path Reviewed
superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/types.ts
superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/EchartsWaterfall.tsx

Explore our documentation to understand the languages and file types we support and the files we ignore.

Need a new review? Comment /korbit-review on this PR and I'll review your latest changes.

Korbit Guide: Usage and Customization

Interacting with Korbit

  • You can manually ask Korbit to review your PR using the /korbit-review command in a comment at the root of your PR.
  • You can ask Korbit to generate a new PR description using the /korbit-generate-pr-description command in any comment on your PR.
  • Too many Korbit comments? I can resolve all my comment threads if you use the /korbit-resolve command in any comment on your PR.
  • On any given comment that Korbit raises on your pull request, you can have a discussion with Korbit by replying to the comment.
  • Help train Korbit to improve your reviews by giving a 👍 or 👎 on the comments Korbit posts.

Customizing Korbit

  • Check out our docs on how you can make Korbit work best for you and your team.
  • Customize Korbit for your organization through the Korbit Console.

Current Korbit Configuration

General Settings
Setting Value
Review Schedule Automatic excluding drafts
Max Issue Count 10
Automatic PR Descriptions
Issue Categories
Category Enabled
Documentation
Logging
Error Handling
Readability
Design
Performance
Security
Functionality

Feedback and Support

import thumbnail from './images/thumbnail.png';
import example1 from './images/example1.png';
import example2 from './images/example2.png';
import example3 from './images/example3.png';
import { EchartsWaterfallChartProps, EchartsWaterfallFormData } from './types';
import { EchartsWaterfallChartProps, EchartsWaterfallFormData } from './types-copy';

This comment was marked as resolved.

renderTrigger: true,
default: '25',
isInt: true,
// validators: [v => !Number.isNaN(v) && v >= 0],

This comment was marked as resolved.

Comment on lines +210 to +211
default: '25',
isInt: true,

This comment was marked as resolved.

Comment on lines +68 to +78
name: 'seriesOrderDirection',
config: {
type: 'SelectControl',
label: t('Order Direction'),
choices: [
[null, t('None')],
['ASC', t('Ascending')],
['DESC', t('Descending')],
],
default: null,
renderTrigger: true,

This comment was marked as resolved.

Comment on lines 54 to +55
const eventHandlers: EventHandlers = {
click: params => {
Copy link

Choose a reason for hiding this comment

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

Missing documentation for complex click handler category Documentation

Tell me more
What is the issue?

The click event handler has complex business logic related to filtering and visual state management, but lacks documentation explaining its purpose and behavior.

Why this matters

Without clear documentation of this handler's behavior, future maintainers will have difficulty understanding the filtering logic and its interaction with the chart's visual state.

Suggested change ∙ Feature Preview

const eventHandlers: EventHandlers = {
/**
* Handles click events on chart elements to manage cross-filtering.
* When an element is clicked:
* - Applies filter if element isn't currently filtered
* - Clears filter if element is currently filtered
* - Updates visual state to highlight/unhighlight elements
* - Ignores clicks on 'Total' column
*/
click: params => {

Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.


const getLabelDistanceOptions = (options: EChartsCoreOption) => {
if (Number.isNaN(Number(xAxisLabelDistance))) {
console.error('xAxisLabelDistance should be a number');

This comment was marked as resolved.

Comment on lines +371 to +409
const getAxisRange = (options: EChartsCoreOption) => {
if (orientation === 'vertical') {
const xAxis = options.xAxis as any;
const grid = options.grid as any;

// Get actual chart area width accounting for grid margins
const availableWidth = width - (grid?.left || 0) - (grid?.right || 0);

if (xAxis?.type === 'value') {
const range = xAxis.max - xAxis.min;
return Math.min(range, availableWidth);
}
if (xAxis?.type === 'category') {
const categories = xAxis.data?.length || 1;
// Calculate space per category
return availableWidth / categories;
}
} else {
const yAxis = options.yAxis as any;
const grid = options.grid as any;

// Get actual chart area height accounting for grid margins
const availableHeight =
height - (grid?.top || 0) - (grid?.bottom || 0);

if (yAxis?.type === 'value') {
const range = yAxis.max - yAxis.min;
return Math.min(range, availableHeight);
}
if (yAxis?.type === 'category') {
const categories = yAxis.data?.length || 1;
// Calculate space per category
return availableHeight / categories;
}
}

// Fallback to a reasonable default
return orientation === 'vertical' ? width * 0.8 : height * 0.8;
};

This comment was marked as resolved.

Comment on lines +123 to +132
const updatedSeries = series.map(s => ({
...s,
data: s.data.map((d: any, idx: number) => ({
...d,
itemStyle: {
...d.itemStyle,
opacity: !Number.isNaN(d.value) && idx === valueIndex ? 1 : 0.3,
},
})),
}));

This comment was marked as resolved.

Comment on lines +248 to +259
const sortedData = [...xAxisData];

sortedData.sort((a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
return sortXAxis === 'asc' ? a - b : b - a;
}
const aStr = String(a);
const bStr = String(b);
return sortXAxis === 'asc'
? aStr.localeCompare(bStr)
: bStr.localeCompare(aStr);
});

This comment was marked as resolved.


// Handle text wrapping if needed
const maxWidth = getAxisRange(options); // chart width
const maxCharsPerLine = Math.floor(maxWidth / xTicksWrapLength); // Approx chars per line

This comment was marked as resolved.

@rusackas
Copy link
Member

Can you add some details to the Description so we know the intent of the change? Thanks!

@anantaoutlook
Copy link
Author

anantaoutlook commented Apr 21, 2025

Can you add some details to the Description so we know the intent of the change? Thanks!

Details are attached. Please let me know if you need more info.
waterfall-changes doc.pdf

@rea725
Copy link

rea725 commented Apr 24, 2025

The work performed on Superset by @anantaoutlook in this PR includes the following improvements:

1.) Horizontal waterfall in addition to vertical.
image

2.) Axis label fixes to word-wrap longer labels instead of hiding them. This is extremely important for readability/usability.
For example. Prior to these fixes, roughly half of the following labels in this chart were hidden:
image

3.) Sorting of Waterfall: It is now possible to sort the waterfall by a field other than the field indicated in the waterfall column headers. Again, extremely important. As an example, in the following chart, we are bridging from gross revenue to Profit. It's vital that these grouping appear in the correct order:
image
image

4.) Added option to use first value as subtotal, and bold first value as subtotal. These are important optional settings for the waterfall. In the example, these allow the Gross Revenue bar to be presented as a subtotal (full bar and grey), and the label for Gross Revenue is bold.
Before:
image
After:
image

5.) Cross-filtering is now feasible for the waterfall chart.

@michael-s-molina
Copy link
Member

Thanks @anantaoutlook @rea725 for the great improvements! I updated the PR description with @rea725's comment.

2.) Axis label fixes to word-wrap longer labels instead of hiding them.

ECharts automatically calculates what labels will be displayed to avoid label overlaps when the number of bars is high. I think the solution here would be to provide a checkbox to show all labels (disabled by default). The same feature is currently being discussed in #33191 so please check the comments of that PR.

4.) Added option to use first value as subtotal, and bold first value as subtotal.

Can we assume that if the first value is used as a subtotal, it's always displayed with bold? It's good to avoid additional controls if possible.

@michael-s-molina michael-s-molina changed the title improve waterfall feat: Improves waterfall chart Apr 24, 2025
@michael-s-molina
Copy link
Member

@anantaoutlook Could you also fill the Testing Instructions part of the PR description?

Copy link
Member

@michael-s-molina michael-s-molina left a comment

Choose a reason for hiding this comment

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

First-pass comments. Great work!

name: 'seriesOrderByColumn',
config: {
type: 'SelectControl',
label: t('Order Series By Column'),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
label: t('Order Series By Column'),
label: t('Order series by column'),

name: 'seriesOrderDirection',
config: {
type: 'SelectControl',
label: t('Order Direction'),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
label: t('Order Direction'),
label: t('Order direction'),

default: null,
renderTrigger: true,
description: t(
'Ordering direction for the series, to be used with "Order Series By Column"',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'Ordering direction for the series, to be used with "Order Series By Column"',
'Ordering direction for the series, to be used with "Order series by column"',

),
mapStateToProps: state => ({
choices: [
[null, t('None')],
Copy link
Member

Choose a reason for hiding this comment

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

It's important that this select does not accept null values and cannot be cleared. It should be set with the x-axis column by default.

name: 'show_total',
config: {
type: 'CheckboxControl',
label: t('Show Total'),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
label: t('Show Total'),
label: t('Show total'),

name: 'bold_sub_total',
config: {
type: 'CheckboxControl',
label: t('Bold first value as subtotal'),
Copy link
Member

Choose a reason for hiding this comment

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

Another option would be to have a control like "Bold totals and subtotals".

config: {
type: 'TextControl',
label: t('X Axis Label Distance'),
description: t('Distance of the label from X axis (in pixels)'),
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this? ECharts automatically calculates the distances.

renderTrigger: true,
default: '25',
isInt: true,
// validators: [v => !Number.isNaN(v) && v >= 0],
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// validators: [v => !Number.isNaN(v) && v >= 0],

@@ -133,6 +242,53 @@ const config: ControlPanelConfig = {
description: t('The way the ticks are laid out on the X-axis'),
},
},
{
name: 'x_ticks_wrap_length',
Copy link
Member

Choose a reason for hiding this comment

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

Can't this be automatically calculated?

renderTrigger: true,
default: '25',
isInt: true,
// validators: [v => !Number.isNaN(v) && v >= 0],
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// validators: [v => !Number.isNaN(v) && v >= 0],

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.

4 participants