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

Chartjs v2.8 removes latest label on line chart #6154

Open
yusufozturk opened this issue Mar 21, 2019 · 28 comments
Open

Chartjs v2.8 removes latest label on line chart #6154

yusufozturk opened this issue Mar 21, 2019 · 28 comments

Comments

@yusufozturk
Copy link

yusufozturk commented Mar 21, 2019

Hi,

Chartjs 2.8 removes latest label which makes it harder to understand the chart. I can confirm that this behaviour started with version 2.8. I added two codepens for v2.7 and v2.8 so you can see the difference.

We do not use time scale. Date comes as string from backend and we handle the zooming operations from backend as well.

Here is how it looks in our product:

v2.7:
image

v2.8:
image

Expected Behavior

User should see latest label on line chart.

Here is the correct view from v2.7:
https://codepen.io/yusufozturk/pen/NJEKpw

I see latest label here:
image

Current Behavior

Chartjs 2.8 removes the latest label, and so there is an empty space at the end of the labels.

Here is the issue from v2.8:
https://codepen.io/yusufozturk/pen/Lagwvo

Label is visible on chart but not on the axis:
image

Possible Solution

I will revert back to v2.7. I don't have any other solution at the moment.

Steps to Reproduce (for bugs)

Create a chart with lots of data points and try to limit width of the chart. So Chartjs will remove some of the labels to fit all labels in to the chart area. But latest version will also remove the latest label.

Environment

  • Chart.js version: 2.8
  • Browser name and version: Chrome 72.0.3626.121
@simonbrunel
Copy link
Member

This change comes from #5891, considered as a bug fix because the "always display last tick" was reported many times as an issue and because the initial reason of its implementation was a bit obscure. It also made the auto skip behavior weird and not aesthetic in many cases.

In 2.8, we decided to "fix" it by making the auto skip logic consistent for all ticks without any special case for the last one. Though, I agree we should support such design, however, the API shouldn't be dedicated to this specific use case, thus I doubt we will consider options like alwaysShowLastTick.

Some suggestions that would need to be debated:

autoSkipReverse: true // skip ticks from end to start

// and/or 

autoSkip: function({tick, index, ticks}) {
    return undefined; // let the auto-skipper decide
    return true;      // skip it
    return false;     // keep it
}

We could also start nesting auto skip specific options under autoSkip:

autoSkip: {
    reverse: true,
    padding: 4
}

@yusufozturk
Copy link
Author

@simonbrunel I understand the pain here, but if we are going to implement a realtime chart with ChartJs, we must show latest tick. Otherwise end user might think, realtime chart is not in real time, and it's going a few seconds late. That's why it's very important to show last tick. (We update charts every 500 ms, so we can show performance stats in realtime)

I also understand It's not easy to offer something which can make everyone happy. But I think it's nice to put an option for backward compability for other people who likes to keep the same behaviour in the future. (Please don't get me like I'm complaining, but consider this as a feedback) Thank you for your great efforts. We love ChartJs :) 💯

@Hedva
Copy link

Hedva commented Mar 21, 2019

The reason I don't want to use 2.8 right now because the last label can dissapear

@benmccann
Copy link
Contributor

I wonder if you could just handle manually by removing the last label and adding a new one with the max value of the axis

@yusufozturk
Copy link
Author

@benmccann This is one of our real time chart. I think, we do the same way, removing last label and adding a new date label as string. So if customer looks into this, he does not see latest date. In this case, he might think that, real time chart is 3-4 seconds behind of the time.

realtime

Also we see now, with v2.8, tooltip squares are empty:

image

It's probably something is changed with 2.8 and we do not send background color of the tooltip or point from the backend. I think that's why it's empty but we will fix it.

@yusufozturk
Copy link
Author

@simonbrunel Thanks for pointing out the commit. I changed that "skipping" line and now I see the latest point.

image

I believe there will be an option to enable this in the next release. Otherwise we can use custom code.

@nagix
Copy link
Contributor

nagix commented Mar 23, 2019

@yusufozturk chartjs-plugin-streaming may fit your use case as ticks move together with lines and users will easily know it's realtime.

@nagix
Copy link
Contributor

nagix commented Mar 23, 2019

For empty tooltip squares, set borderDash, pointStyle, pointHoverBorderColor, pointHoverBackgroundColor and pointBackgroundColor to undefined instead of null, or remove all of them from the config.

@seantott
Copy link

seantott commented Apr 1, 2019

I am having the same issues as @yusufozturk as we are also using live data. However, my current fix was to redesign the _autoSkip function to be the code below. As a temporary fix I am using a plugin to swap out the current function on each of the relevant axes.

Note: Since we are only using this for the X Axis, this only affects that, however this could be adapted to include yAxis support.

function newAutoSkip(ticks) {
  var me = this;
  var optionTicks= (me.options && me.options.ticks && me.options.ticks.minor) || {};
  var innerCount = 0;
  varmaxTicks = optionTicks.maxTicksLimit;

  // Axis length
  var axisLength = me.width - (me.paddingLeft + me.paddingRight);

  var result = [];

  if ((me._tickSize() * ticks.length) > axisLength) {
    innerCount = Math.floor(axisLength / me._tickSize()) - 2;
  } else {
    innerCount = maxTicks - 2;
  }

  if (maxTicks && ticks.length > maxTicks) {
    innerCount = Math.min(innerCount, maxTicks);
  }
  var displayInterval = Math.ceil((ticks.length + 1) / (innerCount + 1));
  for (var i = 0; i < ticks.length; i++) {
    if (displayInterval > 1 && i % displayInterval > 0 && i !== (ticks.length - 1)) {
      delete ticks[i].label;
    } else if ((displayInterval < 1 || isNaN(displayInterval)) && i !== (ticks.length - 1)) {
      delete ticks[i].label;
    }
    result.push(ticks[i]);
  }
  return result;
}

I really like @simonbrunel's suggestion of adding a custom autoSkip function, and I also think that having an autoSkip set of options under the ticks would be very useful to save coding and present multiple implementations. Happy to help implement this.

@gerardhoogland-united
Copy link

Agree: The last label contains very important information in our case, and when the last label is omitted, our graph does not make much sense. We would appreciate an extra option to tune the autoSkip behaviour, to include the last label (versus: spread ticks evenly)

@aldipower
Copy link

Having the same problem here.
The last tick is absolutely mandatory in my charts and cannot be omitted.
Already tried some dirty tricks, but they introduce other problems, so an option like alwaysDisplayLastTick would be great, getting back to old behavior from 2.7.0.
Only option I currently have is to downgrade Chart.js.
I cannot understand why this breaking change from 2.7.0 to 2.8.0 was actually introduced as a breaking change and not as an additional option.

@aldipower
Copy link

aldipower commented Oct 8, 2019

My charts reflecting endurance sports activities having 2 x-scales for distance and duration/time.
The y-scales displaying data such as heartrate and so on.
As a runner or cyclist you're interested in how far you made it or how long you took, so the last tick is actually the most important one of the whole chart. :)
For me it's also more aesthetic having an uneven gap rather then skipping the last tick.

But of course I can understand the reasons for the new autoskip behaviour as well and for other use cases they absolutely makes sense.

So, why not just offering both options?
The situation now is a breaking change in a minor version change, which also could considered as a bug by some people.

@yusufozturk
Copy link
Author

Hi @simonbrunel, do you have any quick workaround for the latest version to show last label?

Apparently autoSkip code is changed and I can't make it work anymore.

Thanks!

@zailleh
Copy link

zailleh commented Mar 9, 2021

I've got the same problem. I've implemented a workaround... that is to use the following method as an afterFit callback in conjunction with disabling autoSkip in the axis configuration. This doesn't support major ticks as I'm only using minor ticks, but it does the trick.

In essence, you'll always get first and last ticks, and if they fit, you'll get ticks in-between up to the maxTicksLimit configuration.

afterFit(axis) {
  const { options, _labelSizes: labelSizes } = axis;
  const ticks = axis.getTicks();

  const widest = labelSizes.widest.width;
  const chartWidth = axis.width;
  const { autoSkipPadding, maxTicksLimit } = options.ticks;

  const maxFit = Math.trunc(chartWidth / (widest + autoSkipPadding));
  const willFit = Math.min(maxFit, maxTicksLimit || 11);

  const validLabelIndices = new Set();
  validLabelIndices.add(0);
  validLabelIndices.add(ticks.length - 1);

  const numLabels = ticks.length % 2 === 0 ? willFit : willFit - 1;

  const interval = ticks.length / (numLabels - 1);
  for (let i = 1; i < willFit - 1; i += 1) {
    validLabelIndices.add(Math.floor(interval * i));
  }

  ticks.forEach((tick, index) => {
    Object.assign(
      tick,
      { label: validLabelIndices.has(index) ? tick.label : null },
    );
  });
}

Example result:

before (built-in autoSkip) after (custom afterFit callback)
image image

@jsphpndr
Copy link

jsphpndr commented Oct 5, 2021

I've got the same problem. I've implemented a workaround... that is to use the following method as an afterFit callback in conjunction with disabling autoSkip in the axis configuration. This doesn't support major ticks as I'm only using minor ticks, but it does the trick.

In essence, you'll always get first and last ticks, and if they fit, you'll get ticks in-between up to the maxTicksLimit configuration.

afterFit(axis) {
  const { options, _labelSizes: labelSizes } = axis;
  const ticks = axis.getTicks();

  const widest = labelSizes.widest.width;
  const chartWidth = axis.width;
  const { autoSkipPadding, maxTicksLimit } = options.ticks;

  const maxFit = Math.trunc(chartWidth / (widest + autoSkipPadding));
  const willFit = Math.min(maxFit, maxTicksLimit || 11);

  const validLabelIndices = new Set();
  validLabelIndices.add(0);
  validLabelIndices.add(ticks.length - 1);

  const numLabels = ticks.length % 2 === 0 ? willFit : willFit - 1;

  const interval = ticks.length / (numLabels - 1);
  for (let i = 1; i < willFit - 1; i += 1) {
    validLabelIndices.add(Math.floor(interval * i));
  }

  ticks.forEach((tick, index) => {
    Object.assign(
      tick,
      { label: validLabelIndices.has(index) ? tick.label : null },
    );
  });
}

Example result:

before (built-in autoSkip) after (custom afterFit callback)
image image

Do you have a working example of this anywhere? Codepen or elsewhere?

@thplat
Copy link

thplat commented Nov 5, 2021

The same happens in v3. Is there any elegant solution to this meanwhile?

@Makarish-naik
Copy link

Makarish-naik commented Apr 6, 2022

i made some changes to @jsphpndr suggestion with autoSkip:true. this is working for me

afterFit(axis) {
  const { options, _labelSizes: labelSizes } = axis;
  const ticks = axis.getTicks();

  const widest = labelSizes.widest.width;
  const chartWidth = axis.width;
  const { autoSkipPadding, maxTicksLimit } = options.ticks;

  const maxFit = Math.trunc(chartWidth / (widest + autoSkipPadding));
  const willFit = Math.min(maxFit, maxTicksLimit || 11);

  const validLabelIndices = new Set();
  validLabelIndices.add(0);
  validLabelIndices.add(ticks.length - 1);

  const numLabels = ticks.length % 2 === 0 ? willFit : willFit - 1;

  const interval = ticks.length / (numLabels - 1);
  for (let i = 1; i < willFit - 1; i += 1) {
    validLabelIndices.add(Math.floor(interval * i));
  }

  ticks.forEach((tick, index) => {
    Object.assign(
      tick,
      { label: validLabelIndices.has(index) ? tick.label : null },
    );
  });
   axis.ticks = ticks.filter(i => i.label)

}

@dannypk
Copy link

dannypk commented Sep 9, 2022

hey guys, this is open for 3 years now, any fix? I have a bunch of years and the last one doesn't always show up, it ends at 2098 or 2099, although the last label is 2100. Changing some options might look fine, 2100 showing, but on a different resolution (zooming in / out), it might disappear again.
Thanks again for your efforts and support

@markusand
Copy link

This is specially annoying when align: inner and autoSkip results in few ticks and

Captura de pantalla 2023-01-03 a les 12 56 10

@annavanbohemen
Copy link

@dannypk found a solution in the last months? can't find anything online..

@dannypk
Copy link

dannypk commented Jan 25, 2023

@annavanbohemen hi! I found 2 solutions - one was, having fewer labels! Although not a proper solution, our use case changed, and I had to display each year between 2020 to 2100. Now, we have to show up to 2050 instead of 2100, so there are fewer labels, therefore the chart looks proper now.

Anyway, what really helped was changing the label's tick rotation.
Here's how to do it, I hope it helps!

options = { 
    ... yourOptions, 
   scales: {
       x: { 
         ticks: {
           autoSkip: true,
           maxRotation: 90, (these are degrees)
           minRotation: 30
         }
      }
   }
 }

At 90 degrees it will look like in this screenshot
Screenshot 2023-01-25 at 12 06 38

Can find more about it in docs https://www.chartjs.org/docs/latest/axes/cartesian/_common_ticks.html

@annavanbohemen
Copy link

@dannypk Thanks!
but unfortunately it still doesn't show the last tick when on mobile device. the only way that it shows it always is with ticks.source: data , but then it wil show all ticks and will not skip any. Also not very pretty.

@dannypk
Copy link

dannypk commented Jan 25, 2023 via email

@annavanbohemen
Copy link

annavanbohemen commented Jan 25, 2023

Can you filter the ticks.source's data? And give less options? Like just the one you want to display?  On Wed, Jan 25, 2023 at 17:53, Anna van @.> wrote: @dannypk Thanks! but unfortunately it still doesn't show the last tick when on mobile device. the only way that it shows it always is with ticks.source: data , but then it wil show all ticks and will not skip any. Also not very pretty. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.>

It gets string ‘data’ and nog object data

asfgit pushed a commit to apache/impala that referenced this issue Apr 5, 2023
Updates Chart.js to 2.9.4 for bug fixes and to avoid prototype
pollution. Added Chart.min.js from
https://github.com/chartjs/Chart.js/releases/tag/v2.9.4. Moment.js from
bundle does not seem to be needed.

Chart.js 2.8 introduced chartjs/Chart.js#6154,
which causes the last X-axis label with "and above" to always be
omitted. Add a custom autofit function to calculate our own spacing.

Testing:
- Examined /admission page with multiple queries admitted. Graph appears
  to render correctly.

Change-Id: Ia4e0da58755b9561ddc75c217ef227a81c80a2a6
Reviewed-on: http://gerrit.cloudera.org:8080/19683
Reviewed-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
Tested-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
@martinpagesaal
Copy link

martinpagesaal commented Jun 19, 2023

Hi guys, I'm finding myself working in similar issues.

If you set inside ticks this 2 options you should be good to go.

scales: {
  ...
  x: {
    ...
    ticks: {
      ...
      autoSkip: true,
      maxRotation: 0,
    },
  },
},

This will remove un necessary ticks for x and make sure they are horizontal. (Same logic could be applied for y).

Along this you can also add autoSkipPadding to set how far away you want one label from the other.

@AmirHmZz
Copy link

@simonbrunel 2024 arrived and we still suffer from lack of alwaysShowLastTick option...

@martsInDenIs
Copy link

Hey everyone! It worked for me. I use react-chartjs-2 v.5.2.0 and chart.js v4.4.0.In my case, some_tick_value equal to labels.length - 1 and tick_label equal to labels.at(-1).

Might be helpful 🙏

  scales: {
        ...
        x: {
          beforeFit(axis) {
            axis.ticks.push({ value: <some_tick_value>, label: <tick_label> });
          }
        ticks: {
           autoSkip: true,
           maxRotation: 0,
           autoSkipPadding: <some_padding_value>,
         },
         ...
       }

@perplexed99
Copy link

perplexed99 commented Mar 27, 2024

I've tried to resolve this before but never found this thread and always gave up. My heart sank as I read through, but the very last post, from just last week, is simple and worked perfectly in Chart 3.9.1. Thanks to martsInDenIs.
I used
beforeFit: function(axis) { lbs = axis.chart.config._config.data.labels; len = lbs.length-1; axis.ticks.push({ value: len, label: lbs[len] }); },
However, I think there should be a simple, documented, switch for this. I won't remember this thread in a year or two when I want to do it again.

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

No branches or pull requests