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

Dynamically Adjust Items Shown in AxisBottom based on width of chart #2005

Closed robd003 closed 2 years ago

robd003 commented 2 years ago

Does anyone know of a way to dynamically adjust the number of items shown in AxisBottom depending on the width of the chart?

Here's an example of a chart stretched out wider than most displays and you can see every tick: Screen Shot 2022-05-16 at 3 12 28 PM

Here's an example of a chart at a "normal browser width" and the X-axis is unreadable: Screen Shot 2022-05-16 at 3 12 17 PM

I'm aware of the trick described in ticket https://github.com/plouc/nivo/issues/524#issuecomment-628296620 where you can use formatter to omit values, but is there a way to have this vary depending on the width of the chart?

Honestly I wish Nivo would just automatically show the appropriate number of items in the X-axis.

tkonopka commented 2 years ago

Could it be that the chart in this example is using a categorical axis rather than a continuous one?

On a categorical axis, elements are independent entities and their positions along the axis are either accidental or a design choice. Omiting any of the labels risks losing meaning, so the chart draws all the labels even if that makes the text fragments overlap.

In the example images, it seems the values on the axis are dates. If you can turn the scale into a time scale, there will be more flexibility with the ticks on the axis. Setting tickValues: 5, for example, should give a small set of labels that fit without overlap.

See here for sample code.

Admittedly, labels could still get squeezed if the display becomes even narrower.

alex20044 commented 2 years ago

This is an essential part of the chart. There should be more focus on implementing dynamic labels that do not overlap.

tkonopka commented 2 years ago

Hi @alex20044. There are already several ways to customize the tick labels. See here for an example. Note how there are more data points on the lines than labels on the axis, and how the labels are well-spaced and legible.

The same approach as in the linked example can probably apply to the data in the screenshots. The key point is to set the horizontal scale to a continuous time scale (not a categorical scale). After that, my guess would be to try tickValues: 5 to set the labels. That should suit both the narrow and the wide formats. For charts that are much smaller than in the screenshot, you can also try to display tick labels at an angle with tickRotation (in degrees) and tickPadding (in pixels).

stale[bot] commented 2 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!

stale[bot] commented 2 years ago

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!

sebastienbarre commented 10 months ago

There are already several ways to customize the tick label

@tkonopka Unfortunately, this works in this example because the time scale is supported for this type of chart. Support for any form of responsible labels on the bottom axis is sorely lacking on bar charts for example.

Glidias commented 3 months ago

Here is something that may not be the most optimised (uses iterative trial and error approach), but gets the job done. Wish this feature is built in within Nivo isntead of having to implement your own layers.

const _BottomAxis = ({xScale, yScale, innerWidth, innerHeight}: BarCustomLayerProps<any>) => {

    const minGapDistance = .... // specify here
    const totalTicks = ... // specify here accordingly to known, or typically bars.length from  BarCustomLayerProps
    const innerLength = innerWidth

     let count: number | null = useMemo(() => {
        if (minGapDistance === null) return null
        let gapSize = innerLength / totalTicks
        const gotSkips = totalTicks > 2 && gapSize < minGapDistance
        if (gotSkips) {
            let ticksToFill = totalTicks - 1

            let count = 2;
            let modifiedGapSize = gapSize;
            while (count < ticksToFill) {
                modifiedGapSize = count * gapSize
                if (modifiedGapSize >= minGapDistance &&
                (ticksToFill % count) === 0 &&
                ticksToFill / count * count === ticksToFill) {
                    return count;
                }
                count++
            }
            return 0
        } else return null
    }, [innerLength, minGapDistance, totalTicks])

        const axis = <Axes
            yScale={yScale}
            xScale={xScale}
            width={innerWidth} height={innerHeight}
            bottom={{
                ......
                renderTick: ((props: AxisTickProps<ScaleValue>) => {
                    const isHidden = count !== null &&
                    totalTicks > 2 && props.tickIndex > 0
                    && props.tickIndex < totalTicks - 1 && (props.tickIndex % count) !== 0;
                    return <g style={isHidden ? {visibility: "hidden"} : undefined}>
                        <AxisTick {...props} />
                    </g>
                })
            }}
         />
        return axis
    }
blake-rouse commented 2 months ago

We've been having this same issue. @Glidias going to try your code out and see if we can get it to work. Pretty crazy that this hasn't been implemented by NIVO yet.

plouc commented 2 months ago

@blake-rouse, I don't really understand what's "pretty crazy" about it. Did you try to contribute in any way to the feature?