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

Modifying legend text / label #791

Closed oscargws closed 4 years ago

oscargws commented 4 years ago

I need to be able to update the legend text in order to add some formatting features for readability. Is this currently possible to do in a bar / pie chart?

Something like:

               legends={[
                    {
                        dataFrom: 'indexes',
                        anchor: 'top-right',
                        direction: 'column',
                        justify: false,
                        translateX: 450,
                        translateY: 0,
                        itemsSpacing: 12,
                        itemWidth: 380,
                        itemHeight: 20,
                        itemDirection: 'left-to-right',
                        itemOpacity: 1,
                        symbolSize: 16,
                        legendFormat: d => d.value + d.id, // attempting to format here
                        symbolShape: ({ x, y, size, fill, borderWidth, borderColor }: LegendProps) => (
                            <LegendSymbol x={x} y={y} size={size} fill={fill} borderWidth={borderWidth} borderColor={borderColor} />
                        )
                    }
                ]}

However legendFormat seems to have no effect.

wyze commented 4 years ago

You would set the data property on the object in the legend. There you set a label property and that is what is used.

It is typed as the following shape, but I guess label needs to be added there.

        data?: Array<{
            id: string | number
            value: number
            color?: string
            fill?: string
        }>
nerdess commented 4 years ago

i've got the same issue, i'd like to customize what is displayed in the legend of a pie chart.

@wyze can you please post a more detailed example as i got trouble following your suggestion. e.g. i am not sure what you mean with "set the data property on the object in the legend"....

this is what i got so far (only the relevant stuff)...

current behavior: the legend displays the id. expected behavior: the legend should display what is inside "label".

     var data = [
      {
        id: 1,
        label: "freddy",
        value: 53,
      },
      {
        id: 2,
        label: "mary",
        value: 14
      },
      {
        id: 3,
        label: "lisa",
        value: 8
      },
      {
        id: 4,
        label: "john",
        value: 13
      },
     {
        id: 5,
        label: "bob",
        value: 13
      }
     ];
                 legends={[
                    {
                        anchor: 'right',
                        direction: 'column',
                        translateY: 0,
                        itemWidth: 100,
                        itemHeight: 18,
                        itemTextColor: '#999',
                        symbolSize: 18,
                        symbolShape: 'circle'
                    }
                ]}
wyze commented 4 years ago

Here is another way to do it, where you render the legend manually: https://codesandbox.io/s/nivopie-typescript-4qvmh

dkim-thomas commented 4 years ago

Hey @wyze,

Thanks for providing a codesandbox rendering the legend manually (your response to @nerdess). How would you map the colors properly if you were using Nivo's color scheme? For example my responsive pie chart component passes the prop colors = { scheme: 'category10' }, but I don't seem to have access to the colors within my the component

wyze commented 4 years ago

@dkim-thomas You can do something like this, which uses the same data from the CodeSandbox:

import { categoricalColorSchemes } from '@nivo/colors'

const { category10 } = categoricalColorSchemes

const data = [
  {
    id: 'css',
    label: 'css (custom)',
    value: 410
  },
  {
    id: 'sass',
    label: 'sass',
    value: 175
  },
  {
    id: 'javascript',
    label: 'javascript',
    value: 128
  }
].map((item, index) => ({ ...item, color: category10[index] }))
dkim-thomas commented 4 years ago

Thank you @wyze

Appreciate the rapid response! Is there a way to use the legend prop on the ResponsivePie component to achieve a custom legend label? I saw other PR that allowed for this with bar-graphs and such but as I attempted to use the same strategy for ReponsivePie it failed.

Example: image

Similar Issues that I found: #794 #797 (not sure where this landed)

wyze commented 4 years ago

Yes, we need to get that merged in so it can be in the next release, because the pie chart will currently only use the id for the label.

wyze commented 4 years ago

I merged a fix, so this will be out in the next release.

anirudh-0 commented 4 years ago

Hi @wyze , I think the data property on a legend is now non-functional as it get replaced by getLegendData in "Bar.js". Is there an existing alternative to achieve this currently?

wyze commented 3 years ago

@anirudh-0 I can't find where bar supported a custom label. Currently you would need to render this as a custom layer to replace the legend layer.

matchatype commented 3 years ago

Here is another way to do it, where you render the legend manually: https://codesandbox.io/s/nivopie-typescript-4qvmh

@wyze Could you please provide an example for radar charts too? I'm unable to customize legends.

mboutin-qohash commented 3 years ago

This is not legend formatting but setting a label on a data. It's really not the same and just make another point that this lib is not mature for good chart solution

dubzzz commented 2 years ago

@wyze I know you closed the issue a while ago but I'm wondering why Line charts are not offering an option like legendLabel as Bar charts do 🤔

In the code of packages/line/src/hooks.js, we can see the following:

    const { legendData, series } = useMemo(() => {
        const dataWithColor = data.map(line => ({
            id: line.id,
            label: line.id,       // <---- why can't we pass something to pass a label instead? or map to a label
            color: getColor(line),
        }))
        const series = dataWithColor
            .map(datum => ({
                ...rawSeries.find(serie => serie.id === datum.id),
                color: datum.color,
            }))
            .filter(item => Boolean(item.id))
        const legendData = dataWithColor
            .map(item => ({ ...item, hidden: !series.find(serie => serie.id === item.id) }))
            .reverse()

        return { legendData, series }
    }, [data, rawSeries, getColor])

While in bar we have:

    const legendsWithData: [BarLegendProps, LegendData[]][] = useMemo(
        () =>
            legends.map(legend => {
                const data = getLegendData({
                    bars: legend.dataFrom === 'keys' ? legendData : bars,
                    direction: legend.direction,
                    from: legend.dataFrom,
                    groupMode,
                    layout,
                    legendLabel,         // <---- this can be used to map ids to proper labels
                    reverse,
                })

                return [legend, data]
            }),
        [legends, legendData, bars, groupMode, layout, legendLabel, reverse]
    )

Any reason not to offer something similar for Line? (mostly asking in case the feature could be a thing you could want to be implemented and added)

Here is an example with Bar charts: ```ts import { Bar } from "@nivo/bar"; export default function App() { return ( (d.id === "id-001" ? "France" : "England")} legends={[ { dataFrom: "keys", anchor: "bottom-right", direction: "column", justify: false, translateX: 120, translateY: 0, itemsSpacing: 2, itemWidth: 100, itemHeight: 20, itemDirection: "left-to-right", itemOpacity: 0.85, symbolSize: 20 } ]} axisBottom={{ format: (v) => (v === "ref-001" ? "Banana" : "Watermelon") }} /> ); } ```

Link: https://codesandbox.io/s/angry-ganguly-k07so?file=/src/App.js

Binb1 commented 2 years ago

@wyze to add to @dubzzz point, it looks like using the pattern you suggest won't work on canvas mode.

legends: [
            {
              dataFrom: 'keys',
              data: items.map((item, index) => ({ //Does not work if the chart is a canvas
                id: item.id,
                label: item.label
                color: item.color,
              }),
             ...

This will only render the ids in canvas mode.

rubeonline commented 2 years ago

You can use the legendLabel property on the chart:

 <ResponsiveBar
    legendLabel={(x) => MyCustomLabel ${x.value}}
    ...
</ResponsiveBar>

It will render: MyCustomLabel 200