plouc / nivo

nivo provides a rich set of dataviz components, built on top of the awesome d3 and React libraries
https://nivo.rocks
MIT License
13.19k stars 1.03k forks source link

TickValues set to "every two days" but display both 01/31 and 02/01 #1410

Closed cfecherolle closed 3 years ago

cfecherolle commented 3 years ago

Describe/explain the bug I have a Line Chart set with a Date Bottom Axis, which has tickValues set to "every 2 days". Even with this parameter, while most of the ticks on my chart are displayed accordingly (every two days) I still have two ticks for 01/31 and 02/01, which makes the tick labels look all garbled: they overlap each other. (see screenshot)

Expected behavior I expected to have a tick every two day on the bottom axis.

Screenshots

Capture d’écran 2021-02-05 à 11 49 41

Desktop

Additional context I haven't added any code example, as this is a business case in the company I'm working at and I would have first to obfuscate the data and code. Hopefully, this gives enough information to reproduce and understand the issue. If not, I will take some time to add a sandbox, just ask! :) Also, I'm French so labels are formatted using %d/%m format, this is expected and wanted ;)

m1yon commented 3 years ago

I have a very similar issue, it seems to occur during month transitions. I get the same behavior with any of the time based tickValues.

edit: I believe this issue is the same as #1101

edit2: I've realized it's just easier to provide tickValues an array of Dates to fix this (and gives more flexibility). Here's a function I wrote to generate our tickValues (may be overkill for your use case, but works well for our case that uses a dynamic date range picker).

Note: you should probably memoize this before throwing it into your chart's props.

import { eachDayOfInterval, eachMonthOfInterval } from 'date-fns'
import _ from 'lodash'

type calculateTimeScale = (
  data: {
    y: number
    x: Date
  }[]
) => Array<Date>

const calculateTimeScale: calculateTimeScale = (data) => {
  const minDate = _.minBy(data, (item) => new Date(item.x).getTime())?.x
  const maxDate = _.maxBy(data, (item) => new Date(item.x).getTime())?.x

  const intervalInDays = eachDayOfInterval({
    start: minDate || new Date(),
    end: maxDate || new Date(),
  })

  const intervalInMonths = eachMonthOfInterval({
    start: minDate || new Date(),
    end: maxDate || new Date(),
  })

  // every day
  if (intervalInDays.length < 13) return intervalInDays

  // every other day
  if (intervalInDays.length < 26)
    return intervalInDays.filter((_, index) => !(index % 2))

  // every 3 days
  if (intervalInDays.length < 39)
    return intervalInDays.filter((_, index) => !(index % 3))

  // every 4 days
  if (intervalInDays.length < 52)
    return intervalInDays.filter((_, index) => !(index % 4))

  // every 5 days
  if (intervalInDays.length < 65)
    return intervalInDays.filter((_, index) => !(index % 5))

  // every 6 days
  if (intervalInDays.length < 78)
    return intervalInDays.filter((_, index) => !(index % 6))

  // every week
  if (intervalInDays.length < 91)
    return intervalInDays.filter((_, index) => !(index % 7))

  // twice a month (roughly)
  if (intervalInMonths.length < 6)
    return intervalInDays.filter((_, index) => !(index % 15))

  // every month
  return intervalInMonths
}
stale[bot] commented 3 years ago

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

cfecherolle commented 3 years ago

Still an issue (bump!)

cfecherolle commented 3 years ago

Thanks a lot! :)

MattB543 commented 2 years ago

@m1yon

I tried your method of supplying an array of dates like so:

axisBottom={{
format: "%b %d",
tickValues: ["2022-09-05", "2022-09-12", "2022-09-19", "2022-09-26", "2022-10-03", "2022-10-10"]
}}

And all of the dates overlap each other at the first tick:

image

and I get this error: Failed prop type: The prop 'x' is marked as required in 'AxisTick', but its value is 'undefined'

If instead, I put tickValues: 6 it gives me 5 ticks all one day off of my data

image

Do you still solve this issue in the way you showed above?

m1yon commented 2 years ago

@MattB543

I tried your method of supplying an array of dates like so:

axisBottom={{
format: "%b %d",
tickValues: ["2022-09-05", "2022-09-12", "2022-09-19", "2022-09-26", "2022-10-03", "2022-10-10"]
}}

And all of the dates overlap each other at the first tick: image and I get this error: Failed prop type: The prop 'x' is marked as required in 'AxisTick', but its value is 'undefined'

In my example, I'm setting tickValues to an array of Dates, not strings. Have you tried that?

If instead, I put tickValues: 6 it gives me 5 ticks all one day off of my data

image

I would look and see what type tickValues is expecting. The API may have changed since my solution was from a year and a half ago.

Do you still solve this issue in the way you showed above?

I'm not sure, I've switched projects/companies since then :(

MattB543 commented 2 years ago

@m1yon ah, thanks anyway! Would you still recommend Nivo or would you recommend a different simple React charting library for me to try?

m1yon commented 2 years ago

@m1yon ah, thanks anyway! Would you still recommend Nivo or would you recommend a different simple React charting library for me to try?

I think Nivo's great! My old team still uses it, and they've gotten many compliments from customers on how great the charts are.