reactjstypescriptnext.jsnivo-react

Tooltip not displaying data with dynamic keys in BarChart


Description

Hi! I'm working on a bar chart. Everything is functioning as expected, except the default tooltip, which appears empty when I hover over the bars. My chart uses dynamic keys for the legends, which work correctly, but the same keys don't seem to work for tooltips.

Issue

The tooltips are empty despite the bars displaying correctly with dynamically generated keys. The legends display properly using the same set of keys.

Code Snippets

Chart Configuration

<ResponsiveBar
      data={barData}
      keys={chartKeys}
      indexBy="year"
      margin={{ top: 50, right: 130, bottom: 50, left: 60 }}
      padding={0.3}
      valueScale={{ type: "linear" }}
      indexScale={{ type: "band", round: true }}
      colors={{ scheme: "nivo" }}
      borderColor={{
        from: "color",
        modifiers: [["darker", 1.6]],
      }}
      axisTop={null}
      axisRight={null}
      axisBottom={{
        tickSize: 5,
        tickPadding: 5,
        tickRotation: 0,
        legend: "year",
        legendPosition: "middle",
        legendOffset: 32,
      }}
      axisLeft={{
        tickSize: 5,
        tickPadding: 5,
        tickRotation: 0,
        legend: "credito_vigente",
        legendPosition: "middle",
        legendOffset: -40,
      }}
      enableTotals={true}
      labelSkipWidth={12}
      labelSkipHeight={12}
      labelTextColor={{ from: "color", modifiers: [["darker", 1.6]] }}
      legends={[
        {
          dataFrom: "keys",
          anchor: "bottom-right",
          direction: "column",
          justify: false,
          translateX: 120,
          translateY: 0,
          itemsSpacing: 1, // Reduced spacing between items
          itemWidth: 80, // Smaller width
          itemHeight: 15, // Smaller height
          itemDirection: "left-to-right",
          itemOpacity: 0.85,
          symbolSize: 12, // Smaller symbol size
          effects: [
            {
              on: "hover",
              style: {
                itemOpacity: 1,
              },
            },
          ],
        },
      ]}
      role="application"
      ariaLabel="Nivo bar chart demo"
      barAriaLabel={(e) =>
        `${e.id}: ${e.formattedValue} in year: ${e.indexValue}`
      }
    />

Data Handling

 const [dataByYear, setDataByYear] = useState<AñoConSusActividades[]>([]);
  const [chartKeys, setChartKeys] = useState<string[]>([]); // State to store unique keys
  const transformAndSetData = useCallback(
    (data: PresupuestoPorProgramaUNI[]) => {
      const groupedData = data.reduce((acc, curr) => {
        // Find an existing year entry in the accumulated data
        const yearEntry = acc.find(
          (entry) => entry.year === curr.impacto_presupuestario_anio
        );

        // Construct a new activity object
        const activity = {
          actividad_desc: curr.actividad_desc,
          credito_vigente: Number(curr.credito_vigente.toFixed(0)),
        };

        if (yearEntry) {
          // If an entry for the year already exists, push the new activity into this year's activities array
          yearEntry.activities.push(activity);
        } else {
          // If no entry for the year exists, create a new one with the current activity
          acc.push({
            year: curr.impacto_presupuestario_anio,
            activities: [activity],
          });
        }
        return acc;
      }, [] as AñoConSusActividades[]);

      // Update the state with the new grouped data
      setDataByYear(groupedData);

      // Update the chart keys based on the new data
      setChartKeys(extractUniqueKeys(groupedData));
    },
    [setDataByYear, setChartKeys]
  ); // Include these as dependencies

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch("/api/presupuestoUni");
        const jsonData = await response.json();
        const dataRes: PresupuestoPorProgramaUNI[] = jsonData.dataRes;
        console.log("Fetched Data:", dataRes); // Log the raw fetched data
        transformAndSetData(dataRes);
      } catch (error) {
        console.error("Failed to fetch data:", error);
      }
    }

    fetchData();
  }, [transformAndSetData]); // Now only re-runs if transformAndSetData changes, which shouldn’t happen due to useCallback

  const extractUniqueKeys = (data: AñoConSusActividades[]): string[] => {
    const allKeys = new Set(
      data.flatMap((yearGroup) =>
        yearGroup.activities.map((activity) => activity.actividad_desc)
      )
    );
    return Array.from(allKeys);
  };

  //PENSAR SI PUEDO HACER ESTO DIRECTAMENTE EN transformAndSetData
  const barData = dataByYear.map((yearGroup) => {
    // Define the accumulator with an index signature.
    // This tells TypeScript that the object will have any number of properties,
    // where each property key is a string and each property value is a number.
    const activitiesAccumulator: Record<string, number> = {};

    yearGroup.activities.forEach((activity) => {
      activitiesAccumulator[activity.actividad_desc] = activity.credito_vigente;
    });

    return {
      year: yearGroup.year.toString(), // This maps directly to 'indexBy' in the ResponsiveBar component
      ...activitiesAccumulator,
    };
  });

Video of the default tool tip not working:

https://github.com/plouc/nivo/assets/55926702/ac72b3c4-6f12-4faa-8be4-4ef3af06f711


Solution

  • Source: https://github.com/plouc/nivo/discussions/2583#discussioncomment-9310618

    Author of the solution:

    So the tooltips are working, the problem comes from your styles:

    @media (prefers-color-scheme: dark) {
      :root {
        --foreground-rgb: 255, 255, 255;
      }
    }
    
    body {
      color: rgb(var(--foreground-rgb));
    }
    

    By default the text color is white, and so is the tooltip background, that's why it seems empty, you can either adjust your css or pass a theme to the chart:

    theme={{ tooltip: { wrapper: { color: "#000000" } } }}