Bar chart: bars on a temporal x-axis shrink to hairlines when the time grain is finer than the data (7.0.0rc2)
Maintainers usually reply within 2 days
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- react, typescript
- Domain
- data-visualization, frontend
Research direction
Start in superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts at getGrainBarMaxWidth, then read its callers in Timeseries/transformProps.ts and MixedTimeseries/transformProps.ts. Review test/Timeseries/Bar/sparseSubDailyBarGeometry.test.ts and add coverage for Day grain with monthly and yearly data in both chart types. Done when those cases keep the fallback bar width while the existing sub-daily behavior remains covered.
Written by the indexing model from the issue text.
Description
Bug description
(I used Claude AI to assist in writing up an error I encountered - I have read and edited this)
In 7.0.0rc2, a Bar Chart with a temporal x-axis draws its bars about 1px wide when the time grain is finer than the spacing of the data. The common case is the default grain, Day, on data that is already monthly or yearly. In 6.1.0 the same saved chart draws normal-width bars.
Our case is a stacked bar chart with one bar per year. The x-axis column Year holds 2019-01-01 00:00:00, 2020-01-01 00:00:00, …, 2024-01-01 00:00:00. The time grain is the default, Day. In 6.1.0 each bar fills most of the gap between years. In 7.0.0rc2 each bar is a sliver less than 1px wide.
The cause is getGrainBarMaxWidth in superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts, added by #44628. It sets the bar series' barMaxWidth to the width of one grain bucket:
const domainSpanMs =
domainMax > domainMin ? domainMax - domainMin : 2 * ONE_DAY_MS;
return (grainMs / domainSpanMs) * Math.max(plotLengthPx, 0);
For our chart:
- Grain: Day, so 1 day
- Data span: 2019-01-01 to 2024-01-01, about 1,826 days
- Plot width: about 750px
- Result: 750 × 1/1826 ≈ 0.4px
Before #44628, ECharts sized bars on a time axis from the actual gap between data points (one year here), with barMaxWidth: 100 as the cap. The grain didn't affect bar width.
#44628 was meant for sparse sub-daily data, and the comment it added in Timeseries/transformers.ts says so: "For a sub-daily time grain, transformProps.ts overrides this with a grain-derived value". The code doesn't match that comment, though. getGrainBarMaxWidth applies to every grain in TIMEGRAIN_TO_TIMESTAMP, which includes Day, Month, Quarter and Year. The PR's tests in test/Timeseries/Bar/sparseSubDailyBarGeometry.test.ts all use PT1H, so nothing tests the Day-and-coarser cases.
Day is the default for time_grain_sqla, so any saved bar chart of pre-aggregated monthly or yearly data that kept the default will show hairline bars after upgrading. The override runs in MixedTimeseries/transformProps.ts too, so mixed charts with a bar series are affected the same way.
Choosing a coarser grain works around it for our chart, but that isn't a general fix. The grain also truncates the date column in SQL. Data stamped on fiscal-year starts (e.g. July 1) would be moved and possibly merged by a Year grain. Irregularly spaced dates have no grain that fits.
How to reproduce
- Use a dataset with a temporal column holding one value per year (or per month), e.g.
2019-01-01through2024-01-01, and a numeric metric. - Create a Bar Chart with that column as the x-axis. Leave Time Grain at the default, Day. Stacking makes no difference.
- The bars are drawn as hairlines.
- Change Time Grain to Year (or Month for monthly data). The bars are normal width again.
Expected behavior
As in 6.1.0, bars on pre-aggregated data are sized from the spacing of the data whatever the grain setting, so a Day grain on yearly data draws wide bars.
Proposed fix
Apply the grain-based override only to sub-daily grains, as #44628's title and comment describe. In getGrainBarMaxWidth, return undefined (keeping the existing barMaxWidth: 100 fallback and ECharts' own sizing) when the grain is a day or longer:
if (!grainMs || grainMs >= ONE_DAY_MS) {
return undefined;
}
This brings back 6.1.0 behavior for Day and coarser grains and keeps the #44628 fix for the sparse hourly and minute data it targeted. The existing PT1H tests should still pass.
Unit tests should cover a Day grain on yearly and monthly data, checking that barMaxWidth stays at the fallback, in both Timeseries and MixedTimeseries.
I also considered sizing by the larger of the grain and the smallest observed gap between points. That would partly undo #44628, because a gap-sized bar is what spilled across empty buckets in the original sparse sub-daily bug.
Screenshots/recordings
6.1.0 (expected):
7.0.0rc2 (regression), same saved chart:
Chart config (x-axis Year, Time Grain: Day):
Superset version
7.0.0rc2
Checklist
- I have searched Superset docs and Slack and didn't find a solution to my problem.
- I have searched the GitHub issue tracker and didn't find a similar bug report.
- I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
- Dominant language
- Python
- Stars
- 75.2k
- Forks
- 18.5k
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 649
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from apache/superset
-
[MCP] BrowserHelloMiddleware (welcome page to MCP) intercepts every browser GET, breaking OAuth flows of MCP_AUTH_FACTORYPossibly taken @MikolajBalcerek claimed this 4 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
apache/superset#45390 · 1 comment ·
Maintainers usually reply within 2 days
-
get_time_filter Jinja macro returns 500 when the chart has a custom SQL filterPossibly taken @SEPURI-SAI-KRISHNA claimed this 7 days ago. Open🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
apache/superset#45269 · 1 comment ·
Maintainers usually reply within 2 days
-
Nightly full-repo pre-commit fails: mypy Filter|OrFilter narrowing gaps in semantic-layer testsPossibly taken @sadpandajoe claimed this 7 days ago. Openai:fix-master
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 2 days
-
flatten post-processing drops index columns when the index has several levelsPossibly taken @AliIbnBotir claimed this 7 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 2 days
-
RadioButtonControl calls onChange twice per selection (Radio.Group onChange plus a per-button onClick)Possibly taken @FrancescoCastaldi claimed this 18 days ago. Open#bug 🦾 ai-candidate
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 2 days
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
ansys/pyansys-common-mcp#213 ·
Maintainers usually reply within 3 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
freedomofpress/dangerzone#1594 ·
Maintainers usually reply within 3 days
-
priority:P1 status:in-review type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
randomparity/kdive#3317 · 1 comment ·
Maintainers usually reply within 1 day
-
area:ci
Difficulty 1/5 Under an hour Newbie friendliness 90/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
mesonbuild/meson#16281 ·
Maintainers usually reply within 1 day