Skip to content

Conversation

@alexfauquette
Copy link
Member

@alexfauquette alexfauquette commented Oct 3, 2025

Fix #19531

Open topics

Using an attribute

Adding a isNumerical attribute to enable the feature. This attribute is a way for the user to say "my data are number/date" and the are kind of evenly spaced.

For this major the need seems obvious. For next version, it's up to debate.

IMO it's necessary. I'm a conservative person, an ordinal scale should behave as an ordinal scale except if the devs set options saying the opposite.

But I get it's convenient to say "if I provide numbers or date, it's assuming they are evenly spaced. To get categories I need to provide string"

Formatting

After playing a bit with it, I think the formatting function should get the tick value as a first argument. That's because those value are designed to be human readable so the one you want to format.

A remaining issue is the switch between tick values and band values while zooming.

In the demos for now it's hidden by value.toLocaleDateString() which always format ticks in a consistent way.
But if you use the default time formater, the initial ticks are better. Up to the moment you reach the "one tick per band".

The reason is nice ticks set hours to 00:00. So the default d3 formater knows there is no time specify and so ignore it.
The data contains either 01:00 or 02:00 so the default formatter displays the hours which is not the expected behavior.

I need a way to let the user know when we swich from one tick management to the other. But sure yet about how to do that

image image image

Smart switch between linear/band ticks

Currently I propose to do the switch when the number of ticks in linear versus band is 1/3:

Roughly the idea is the following: If we reduce a lot the number of ticks we apply it. If it's nearly the same we use the more accurate.

nb of ticks linear scale nb of bands chosen ticks
5 100 linear
5 15 linear
5 14 band

But I'm open to other criteria. Maybe a band size. Such that if band is more than 20px we display a tick per band. If less than 20px we use linear scale to sub-sample to ticks.

Tick placement

I tried to rely on the theoretical position of ticks (according to the continuous scale to know if I should place them at the beginning/middle/end of the band. But it's not supper nice. For example here it would be better that the tick appears in the middle of the band since it's "exactly" the same value.

image

The root cause was different. The formatted value was the wrong one (the band instead of the tick) so it was of course miss leading.

first/last ticks

The case of the first/last tick is tricky. Because if your data are starting at 1 it would be nice on, a large scale to put a tick 0 before the first band. But 0 is outside of the range.

For now I do it that way:

  • Create a line scale that goes from min to max
  • generate ticks with a nice() version of this linear scale
  • Only keep ticks that appears in the drawing area (green rect) or in a flexxible area (orange rect) wichi now is computed as the width of 2 bands
  • Place ticks as best as possible on the band scale
image

@alexfauquette alexfauquette added type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. scope: charts Changes related to the charts. labels Oct 3, 2025
@mui-bot
Copy link

mui-bot commented Oct 3, 2025

Deploy preview: https://deploy-preview-19808--material-ui-x.netlify.app/

Updated pages:

Bundle size report

Bundle Parsed size Gzip size
@mui/x-data-grid 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-pro 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-premium 0B(0.00%) 0B(0.00%)
@mui/x-charts 🔺+2.15KB(+0.64%) 🔺+664B(+0.66%)
@mui/x-charts-pro 🔺+2.15KB(+0.49%) 🔺+620B(+0.47%)
@mui/x-charts-premium 🔺+2.15KB(+0.49%) 🔺+619B(+0.47%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)

Details of bundle changes

Generated by 🚫 dangerJS against 967c3b6

@codspeed-hq
Copy link

codspeed-hq bot commented Oct 3, 2025

CodSpeed Performance Report

Merging #19808 will not alter performance

Comparing alexfauquette:experiment (967c3b6) with master (6cde4ad)1

Summary

✅ 13 untouched

Footnotes

  1. No successful run was found on master (403a193) during the generation of this report, so 6cde4ad was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@alexfauquette alexfauquette changed the title [charts][draft] Place band ticks according to a continuous scale [charts] Place ordinal ticks according to a continuous scale Oct 7, 2025
@alexfauquette alexfauquette marked this pull request as ready for review October 7, 2025 09:06
@bernardobelchior
Copy link
Member

In the example you provided there's some inconsistent spacing between ticks. The numbers below represent the number of bars between each tick.

image

I think this creates an odd looking chart because space is inconsistent.

Do we know how/if other libraries implement this behavior?

I think Recharts allow you to specify the tick layout preference (i.e., show first and last tick, just first, just last, etc.). If we can't find an optimal solution, we might need to go ahead with something similar.

I'm wondering if we can follow a simpler algorithm: since bars all have the same width, could we use them as the spacing instead of pixels? E.g., all ticks should be spaced by X bars. Maybe we need to adapt d3's nice algorithm or create our own, though.

The downside would be that it doesn't work well for dates, i.e., we likely won't see "nice dates", but I'm not sure nice dates are better than visual consistency. What do you think?

@alexfauquette
Copy link
Member Author

alexfauquette commented Oct 7, 2025

I think this creates an odd looking chart because space is inconsistent.

I'm not sure to get all the proposal you made. The root issue is that there are some empty space in the ticks. So if we go from ticks value we can not guarantee we evenly space them

Going from ticks has an adventage for some zoom level to pick for example the begining of the month. But as soon as you are in an in-between state, I agree it's less interesting.

image image

Another option I've seen is to go the other way around: You fix the tick positions and you pick the closest value. Basically const ticksValues = [0, 1, 2, 3, 4, 5].map(k => scale.invert(left + k/5 * drawingArea.width))

In this recording their is a range where zooming has no impact on the ticks position. Only their value get modified

Capture.video.du.2025-10-07.14-27-25.mp4

I will give a deeper look at how trading charts are handling this

@JCQuintas
Copy link
Member

The downside would be that it doesn't work well for dates, i.e., we likely won't see "nice dates", but I'm not sure nice dates are better than visual consistency. What do you think?

Evenly spaced dates are a bit hard because of how many days there are in months, in some instances you will never be able to plot nice even labels because you are between january and april, and february has 28 for some reason so it will mess everything up 🤷

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Oct 7, 2025
@github-actions
Copy link

github-actions bot commented Oct 7, 2025

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@alexfauquette
Copy link
Member Author

Evenly spaced dates are a bit hard because of how many days there are in months

I propose we open an issue in the international metric institute to ask them a breaking change on the way day and months are organized 😇

@bernardobelchior
Copy link
Member

I think this creates an odd looking chart because space is inconsistent.

I'm not sure to get all the proposal you made. The root issue is that there are some empty space in the ticks. So if we go from ticks value we can not guarantee we evenly space them

What I was suggesting is that we follow what d3 is doing. We provide a suggested tick number and we try to place them.

Let's say there's 19 ticks (prime number, so not easily divisible). If we want 2 ticks we place them such that the number of bars to the left and right of each tick are the same. If there's 19 ticks, we have 17 bars to distribute: for even spacing let's place the first tick in the 6th bar (5 bars distance to the start), then the last tick on the 14th bar (7 bars distance to the first tick, and 5 bars distance to the end). Alternatively, we can make it so there's 6 bars before the first and 6 bars after the last, with a spacing of 5 bars in the middle.

Let's say we want to lay out 3 ticks. This means we have 16 bars left, which means there's a 4 bar spacing between each tick.

For four ticks: 15 bars left: 3 spaces each.
For five ticks: 14 bars left: 2 spaces between middle ticks, 3 spaces before first and after last => 32+24=14.

If there's a number that doesn't work, we could try a greater number, then a smaller one.

I can try to write an algorithm for this, but I think we might be able to make it work. If we change the tick number, there must always be an evenly spaced solution.

In this recording their is a range where zooming has no impact on the ticks position. Only their value get modified

It seems the ticks are changing position slightly, but it does seem there's a fixed number of ticks for that size and they are adjusted slightly.

@JCQuintas
Copy link
Member

Using an attribute

If we want to say that the band is continuous, we might as well use isContinuous instead of isNumeric 😆

For next version I don't mind having it automated, but it needs to make sense

Formatting
The reason is nice ticks set hours to 00:00. So the default d3 formater knows there is no time specify and so ignore it.

Can't we use d3 for the small dates as well? If we provide the visible data to d3 and ask for formatting, wouldn't it provide the values for us?

Smart switch between linear/band ticks

We could accept both, 20px or a ratio 1/2/0.5, the px value is more adequate when you know exactly your data and just want a cleaner view, while the later would be better for unknown data.

Tick placement

Tick placement is annoying, I had to handle it in the grouped axis as well. You could try to put it in the middle of the band when the continuous value is inside the band.

They would be in the correct place, but not evenly spaced though.

first/last ticks

There could be an option to forcibly put the first and/or last ticks in, however value the min/max of the current view is 🤔
Though im not sure

@alexfauquette
Copy link
Member Author

It seems the ticks are changing position slightly, but it does seem there's a fixed number of ticks for that size and they are adjusted slightly.

Yes, I suspect the small modification are due to the tick falling exactly on the value point. So if the zoom level shows 100 of 102 point the pixel value change a bit

If we provide the visible data to d3 and ask for formatting, wouldn't it provide the values for us?

Yes, it will display 1AM every where :)
D3 defaults work well when you let D3 managing both the tick value and the formatter.

The reason being D3 ticks for dates picks a step (year, months, days, hours, ...) according the the data domain. And set all the smaller units to 0. So if the step is months, D3 will generate dates with Date(yea, month, 1, 0, 0, 0).
And the D3 time formatter see that the date is the first millisecond of the month so it uses the month formater.

In out case we have Date(year, month, day, 1, 0, 0, 0) which is seen as the first millisecond of an hour by D3. So it diplay the hour formatter (1AM).

when the continuous value is inside the band.

Easier to say than to do.

Is 2024-002-02T00:00:00Z inside the band with value 2024-002-02T01:00:00Z?
If there is a band per day yes. If there is a band every 2 hours no

@JCQuintas
Copy link
Member

Easier to say than to do.

Is 2024-002-02T00:00:00Z inside the band with value 2024-002-02T01:00:00Z? If there is a band per day yes. If there is a band every 2 hours no

Can't we define if a tick is inside a band or not based on the tick X value?

bandTicks = [
  {x: 0},
  {x: 10},
  {x: 30},
  {x: 50},
]

continuousTick = {x: 12}

band = bandTick.find((v,i,a) => v.x <= 12 && a[i+1].x >= 12 )

@alexfauquette
Copy link
Member Author

Can't we define if a tick is inside a band or not based on the tick X value?

All you know is that tick 12 is between band 10 and band 30. The question is the tick inside the band or between the two is still open. But we can also just say all the ticks are either always between two band or always in the middle of bands

It's easier to consider numbers, because you can always say the 12 should appear between 10 and 30.
But to be similar with date we should think with number like 10.0003 and 11.98897. Because Dates could have a one hour gap.

@JCQuintas
Copy link
Member

JCQuintas commented Oct 8, 2025

Can't we define if a tick is inside a band or not based on the tick X value?

All you know is that tick 12 is between band 10 and band 30. The question is the tick inside the band or between the two is still open. But we can also just say all the ticks are either always between two band or always in the middle of bands

It's easier to consider numbers, because you can always say the 12 should appear between 10 and 30. But to be similar with date we should think with number like 10.0003 and 11.98897. Because Dates could have a one hour gap.

I meant to use the X value of the tick position, it has almost nothing to do with the band 😆
My example is not series/band, I'm dealing only in tick position

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Oct 17, 2025
@alexfauquette
Copy link
Member Author

I looked deeper on most of the financial charts I could think about.

  • It seems the evenly spaced ticks is not an issue. Most of the chart have this. I tend to agree. If a month has a prime number of working days, you still want to display a tick at the begining/end of months plus some ticks inside
  • D3 time scale will not match our needs here. Because it switches from a tick every:
    • 3 months

    • 1 month

    • 1 week

    • 2 days

      and the tick every week does not enforce them to match the start/end of the month

A strategy where tick detect if there is a month change from one value to another looks more promising

https://www.notion.so/mui-org/Charts-ordinal-axis-ticks-management-with-large-data-286cbfe7b66080f99f31dd0c55fad54b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: charts Changes related to the charts. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[charts] Make band/point axis faster for large dataset

4 participants