如何在 rechart 中将工具提示放置在条形图的顶部?

dwp*_*dwp 8 css tooltip bar-chart recharts

问题: 我创建了一个带有自定义工具提示的条形图。现在我需要的是将工具提示放置在栏的顶部而不是图表区域内。就像这张照片一样。

在此输入图像描述

这就是现在的样子。

在此输入图像描述

在这里我提供了我如何组织我的代码。

import React, { Component } from "react";
import {
  BarChart,
  Tooltip,
  Bar,
  Legend,
  ResponsiveContainer,
  Cell
} from "recharts";

import { Card, CardTitle, CardBody } from "reactstrap";

import "./SessionDuration.css";

const colors = ["#26a0a7", "#79d69f", "#f9ec86", "#ec983d"];

const data = [
  {
    name: "Page A",
    pv: 2400,
    amt: 2400
  },
  {
    name: "Page B",
    pv: 1398,
    amt: 2210
  },
  {
    name: "Page C",
    pv: 9800,
    amt: 2290
  },
  {
    name: "Page D",
    pv: 3908,
    amt: 2000
  }
];

const getTitleOfTooltip = (label) =>{
  if (label ===  0) {
    return "<=5 min";
  }
  if (label === 1) {
    return "5-30 min";
  }
  if (label === 2) {
    return "30-60 min";
  }
  if (label === 3) {
    return ">60";
  }
}
const getIntroOfPage = label => {
  if (label ===  0) {
    return "Page A is about men's clothing";
  }
  if (label === 1) {
    return "Page B is about women's dress";
  }
  if (label === 2) {
    return "Page C is about women's bag";
  }
  if (label === 3) {
    return "Page D is about household goods";
  }
};

class SessionDuration extends Component {
  render() {
    return (
      <Card className="session-duration-card">
        <CardTitle className="session-duration-card-header">
          Session Duration
        </CardTitle>
        <CardBody>
          <ResponsiveContainer width="100%" height="100%" aspect={4.0 / 5.5}>
            <BarChart
              data={data}
              margin={{
                top: 3,
                right: 5,
                left: 5,
                bottom: 13
              }}
              barGap={10}
            >
              <Tooltip
                coordinate={{ x: 0, y: 150 }}
                content={<CustomTooltip />}
              />

              <Bar dataKey="pv" fill="#8884d8">
                {data.map((entry, index) => (
                  <Cell key={`cell-${index + 1}`} fill={colors[index]} />
                ))}
              </Bar>
            </BarChart>
          </ResponsiveContainer>
        </CardBody>
      </Card>
    );
  }
}

export default SessionDuration;

const CustomTooltip = ({ active, payload, label }) => {
  if (active) {
    return (
      <div className="session-duration-tooltip">
        <p className="session-duration-tooltip-label">{getTitleOfTooltip(label)}</p>
        <p className="session-duration-tooltip-intro">
          {getIntroOfPage(label)}
        </p>
        <p className="session-duration-tooltip-desc">
          Anything you want can be displayed here.
        </p>
      </div>
    );
  }

  return null;
};
Run Code Online (Sandbox Code Playgroud)

我在这里提供我的 CSS 文件。

@media only screen and (min-width: 1024px) {
  .session-duration-card {
    margin-top: 14.5%;
    margin-left: -44%;
    width: 190% !important;
    border-radius: none !important;
    height: 86%

  }

  .session-duration-card-header {
    font-weight: 700;
    text-align: left;
    padding-left: 6%
  }

  .session-duration-tooltip {
    width: 210%;
  }

  .session-duration-tooltip-label {
    font-weight: 700;
    font-size: 11px;
  }
}

@media only screen and (min-width: 308px) and (max-width: 1024px) {
  .session-duration-card {
    margin-top: 5%;
    margin-left: 3.2%;
    width: 94.5%;
    border-radius: none !important;
  }

  .session-duration-card-header {
    font-weight: 700;
    text-align: center;
  }
}

@media only screen and (min-width: 1666px) {
  .session-duration-card {
    margin-top: 11%;
    margin-left: -13%;
    width: 190% !important;
    height: 97%;
    border-radius: none !important;
  }

  .session-duration-card-header {
    font-weight: 700;
    text-align: center;
  }
}
Run Code Online (Sandbox Code Playgroud)

我尝试了很多方法来找到问题的解决方案,但无法完成,有人可以帮助我修改我的代码来完成这项工作。非常感谢。

Ant*_*ton 5

要解决这个问题,需要获取每个栏和工具提示的高度和宽度。

  1. 需要两个函数onMouseMoveonMouseOut来获取Bar组件的高度和宽度。
  2. 用于useState获取并保持状态的高度和宽度,还向状态放置额外的布尔值,以在鼠标位于 Bar 之外时进行处理。(因此,为了实现功能,我们添加带有两个参数databoolean 的对象)。
  3. Tooltip组件中,为静态值XY设置位置对象,并使用状态值设置高度和宽度。
  4. 使用useEffect, 查找.recharts-tooltip-wrapper类,定义 DOM 元素的高度和宽度。
  5. 设置样式.recharts-tooltip-wrapper. (Rechart.js中.recharts-tooltip-wrapper有自己生成的样式,我们需要保留一些静态样式.recharts-tooltip-wrapper并添加我们的)。

编辑dazzling-code

SessionDuration.js

import React, { useState, useEffect } from 'react';
import { BarChart, Tooltip, Bar, ResponsiveContainer, Cell } from 'recharts';

import { Card, CardTitle, CardBody } from 'reactstrap';

import './SessionDuration.css';

const colors = ['#26a0a7', '#79d69f', '#f9ec86', '#ec983d'];

const data = [
  {
    name: 'Page A',
    pv: 2400,
    amt: 2400,
  },
  {
    name: 'Page B',
    pv: 1398,
    amt: 2210,
  },
  {
    name: 'Page C',
    pv: 9800,
    amt: 2290,
  },
  {
    name: 'Page D',
    pv: 3908,
    amt: 2000,
  },
];

const getTitleOfTooltip = label => {
  if (label === 0) {
    return '<=5 min';
  }
  if (label === 1) {
    return '5-30 min';
  }
  if (label === 2) {
    return '30-60 min';
  }
  if (label === 3) {
    return '>60';
  }
};
const getIntroOfPage = label => {
  if (label === 0) {
    return "Page A is about men's clothing";
  }
  if (label === 1) {
    return "Page B is about women's dress";
  }
  if (label === 2) {
    return "Page C is about women's bag";
  }
  if (label === 3) {
    return 'Page D is about household goods';
  }
};

export const SessionDuration = () => {
  const [position, setPosition] = useState(null);

  useEffect(() => {
    const tooltip = document.querySelector('.recharts-tooltip-wrapper');
    if (!tooltip) return;

    // Init tooltip values
    const tooltipHeight = tooltip.getBoundingClientRect().height;
    const tooltipWidth = tooltip.getBoundingClientRect().width;
    const spaceForLittleTriangle = 10;

    // Rewrite tooltip styles
    tooltip.style = `
      transform: translate(${position?.data.x}px, ${position?.data.y}px);
      pointer-events: none;  position: absolute;
      top: -${tooltipHeight + spaceForLittleTriangle}px;
      left: -${tooltipWidth / 2 - position?.data.width / 2}px;
      opacity: ${position?.show ? '1' : 0};
      transition: all 400ms ease 0s;
    `;
  }, [position]);

  return (
    <>
      <Card className="session-duration-card">
        <CardTitle className="session-duration-card-header">
          Session Duration
        </CardTitle>
        <CardBody>
          <ResponsiveContainer width="100%" height="100%" aspect={4.0 / 5.5}>
            <BarChart
              data={data}
              margin={{
                top: 100, // for tooltip visibility
                right: 5,
                left: 5,
                bottom: 13,
              }}
              barGap={10}
            >
              <Tooltip
                cursor={false} // hide hover effect 'grey rectangle'
                position={{
                  // Static position
                  x: position?.data.x ?? 0,
                  y: position?.data.y ?? 0,
                }}
                content={<CustomTooltip />}
              />

              <Bar
                dataKey="pv"
                fill="#8884d8"
                // Handlers
                onMouseMove={data => setPosition({ data: data, show: true })}
                onMouseOut={data => setPosition({ data: data, show: false })}
              >
                {data.map((entry, index) => (
                  <Cell key={`cell-${index + 1}`} fill={colors[index]} />
                ))}
              </Bar>
            </BarChart>
          </ResponsiveContainer>
        </CardBody>
      </Card>
    </>
  );
};

export default SessionDuration;

const CustomTooltip = ({ active, payload, label }) => {
  if (active) {
    return (
      <div className="session-duration-tooltip">
        <p className="session-duration-tooltip-label">
          {getTitleOfTooltip(label)}
        </p>
        <p className="session-duration-tooltip-intro">
          {getIntroOfPage(label)}
        </p>
        <p className="session-duration-tooltip-desc">
          Anything you want can be displayed here.
        </p>
      </div>
    );
  }

  return null;
};
Run Code Online (Sandbox Code Playgroud)

会话持续时间.css

:root {
  --bg-color: hsl(270 3% 29% / 0.9);
}

.session-duration-tooltip {
  max-width: 250px;
  position: relative;
  padding: 0.5em;
  border-radius: 5px;
  background-color: var(--bg-color);
  color: aliceblue;
}

.session-duration-tooltip::after {
  content: '';
  height: 0;
  width: 0;
  position: absolute;
  bottom: -10px;
  left: 50%;
  transform: translateX(-50%);
  border-style: solid;
  border-width: 10px 10px 0 10px;
  border-color: var(--bg-color) transparent transparent transparent;
}
Run Code Online (Sandbox Code Playgroud)


And*_*erg 1

很多人在这里要求这个功能https://github.com/recharts/recharts/issues/222和这里https://github.com/recharts/recharts/issues/488。我想出了

\n

第三个工作解决方案(在 @\xd0\x96\xd0\xbd\xd0\xb5\xd1\x86\xd0\xaa 答案的大力帮助下)

\n

不幸的是,这个答案的问题是你仍然必须悬停“栏”本身,而我的栏非常窄,所以我希望它显示出来,无论你悬停在“栏”还是“光标区域”,这个解决方案允许后者。

\n

基本上:

\n
    \n
  • 从完整条形图中获取活动工具提示索引(光标区域而不是特定条形上的更改)

    \n
  • \n
  • useEffect 钩子使用 bar 类抓取所有栏".recharts-bar-rectangle",并使用 toolTipIndex 抓取当前活动的栏,获取高度,并将其设置为工具提示 Y 位置。

    \n
  • \n
  • 与之前一样,工具提示由内部 x 位置控制,但 y 位置是高度 +/- 一些偏移。

    \n
  • \n
\n
export default function BarChartCard(props) {\n  const [activeBarIndex, setActiveBarIndex] = useState();\n  const [toolTipYPosition, setToolTipYPosition] = useState({});\n\n\n  useEffect(() => {\n      const barCharts = document.querySelectorAll(".recharts-bar-rectangle");\n      if (!barCharts || !activeBarIndex) return;\n      // Init tooltip values\n      const barChart = barCharts[activeBarIndex];\n      setToolTipYPosition(barChart.getBoundingClientRect().height);\n  }, [activeBarIndex]);\n\n  return (\n    <BaseCard>\n      <Grid container spacing={2}>\n        <Grid item xs={12}>\n          <Typography variant="h2">{props.title}</Typography>\n        </Grid>\n      </Grid>\n      <ResponsiveContainer width={"100%"} height={300}>\n        <BarChart\n          barGap={0}\n          data={props.data}\n          margin={{\n            top: 5,\n            right: 30,\n            left: 20,\n            bottom: 5,\n          }}\n          onMouseMove={(e) => {\n            setActiveBarIndex(e.activeTooltipIndex);\n          }}\n        >\n          <Tooltip\n            content={<CustomTooltip />}\n            cursor={false}\n            position={{ y: 170 - toolTipYPosition }}\n            offset={-60}\n            allowEscapeViewBox={{ x: true, y: true }}\n          />\n          <XAxis\n            style={{\n              fontSize: "1.125rem",\n              fontFamily: "Cairo",\n              fontWeight: 600,\n            }}\n            axisLine={false}\n            tickLine={false}\n            dataKey="date"\n            tickFormatter={ReformatDateShortDayName}\n          />\n          <YAxis\n            allowDecimals={false}\n            style={{\n              fontSize: "1.125rem",\n              fontFamily: "Cairo",\n              fontWeight: 600,\n            }}\n            dataKey="value"\n            axisLine={false}\n            tickLine={false}\n          />\n          <Legend\n            style={{\n              fontSize: "1.125rem",\n              fontFamily: "Cairo",\n              fontWeight: 600,\n            }}\n            iconType="circle"\n            iconSize={6}\n            align="right"\n            verticalAlign="top"\n            height={36}\n            formatter={(value) => (\n              <span\n                style={{\n                  color: "#CCC",\n                  fontSize: "1.125rem",\n                  fontWeight: 600,\n                  fontFamily: "Cairo",\n                }}\n              >\n                {value}\n              </span>\n            )}\n          />\n          <Bar\n            barSize={10}\n            dataKey="value"\n            name={props.legendText}\n            fill="#EFC92B"\n          />\n        </BarChart>\n      </ResponsiveContainer>\n    </BaseCard>\n  );\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

一个黑客解决方案

\n

基于其他一些实现和答案:

\n
const [barGraphData, setBarGraphData] = useState({})\n\nreturn (\n<BarChart barGap={0} data={props.data} margin={{\n            top: 5,\n            right: 30,\n            left: 20,\n            bottom: 5\n            }}>\n    <Tooltip content={<CustomTooltip />}\n    cursor={{ fill: \'transparent\' }}\n    position={{ y: barGraphData.height + 40}}\n    offset={-60}\n    />\n    <XAxis />\n    <YAxis />\n    <Legend />\n    <Bar \n       barSize={10} \n       dataKey="value" \n       fill="#EFC92B" \n       onMouseOver={(data)=> {\n            setBarGraphData(data)\n            }}\n       onMouseLeave={(data) => {\n            setBarGraphData(data)\n            }}\n            />\n</BarChart>\n)\n
Run Code Online (Sandbox Code Playgroud)\n

所以基本上:

\n
    \n
  • 有一个静态 y 值,在这种情况下,我正在计算条形的高度 + 一些偏移量(至少我认为它是条形的高度)。但这也可能是静态的。
  • \n
  • 向工具提示添加偏移量,我必须这样做,否则它不会位于栏的顶部,而是右移 60 像素。
  • \n
  • 让 x 坐标由重新图表处理。
  • \n
\n

正如您从我的代码中看到的,我根据 recharts 文档使用了自定义工具提示

\n
const CustomTooltip = ({ active, payload, label }) => {\n    const classes = useStyles();\n    return (\n        <Fragment>\n            {active && payload?.length >= 1 &&\n                <Card className={classes.toolTip}>\n                    <CardContent className={classes.toolTipContent}>\n                        <Typography className={classes.toolTipLabel}>{label}</Typography>\n                        <Typography className={classes.toolTipValue}>{payload[0].payload.value}</Typography>\n                    </CardContent>\n                </Card>\n            }\n        </Fragment>\n    )\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但这仅适用于格式/样式,它也应该适用于默认格式/样式。

\n

不幸的是,这似乎只有当您将鼠标悬停在实际的“栏”而不是“光标”定义的整个区域时才起作用。

\n

第二个 hacky 解决方案,适用于整个光标区域

\n

这导致我想到了第二个解决方案,不幸的是它给出了警告

\n
Warning: Cannot update a component (`BarChartCard`) while rendering a different component (`label`). To locate the bad setState() call inside `label`, follow the stack trace as described in ...\n
Run Code Online (Sandbox Code Playgroud)\n

创建让它运行的代码就在这里,任何关于如何使这项工作工作的评论都会很棒!

\n
export default function BarChartCard(props) {\n  const [barGraphData, setBarGraphData] = useState({});\n  const [activeBarIndex, setActiveBarIndex] = useState({});\n  const [toolTipYPosition, setToolTipYPosition] = useState({});\n\n  return (\n    <BaseCard>\n      <Grid container spacing={2}>\n        <Grid item xs={12}>\n          <Typography variant="h2">{props.title}</Typography>\n        </Grid>\n      </Grid>\n      <ResponsiveContainer\n        width={"100%"}\n        height={300}\n      >\n        <BarChart\n          barGap={0}\n          data={props.data}\n          margin={{\n            top: 5,\n            right: 30,\n            left: 20,\n            bottom: 5,\n          }}\n          onMouseMove={(e) => {\n            setActiveBarIndex(e.activeTooltipIndex)\n          }}\n        >\n          <Tooltip\n            content={<CustomTooltip />}\n            cursor={false}\n            position={{ y: -toolTipYPosition + 170}}\n            offset={-60}\n            allowEscapeViewBox={{ x: true, y: true }}\n          />\n          <XAxis\n            style={{\n              fontSize: "1.125rem",\n              fontFamily: "Cairo",\n              fontWeight: 600,\n            }}\n            axisLine={false}\n            tickLine={false}\n            dataKey="date"\n            tickFormatter={ReformatDateShortDayName}\n          />\n          <YAxis\n            allowDecimals={false}\n            style={{\n              fontSize: "1.125rem",\n              fontFamily: "Cairo",\n              fontWeight: 600,\n            }}\n            dataKey="value"\n            axisLine={false}\n            tickLine={false}\n          />\n          <Legend\n            style={{\n              fontSize: "1.125rem",\n              fontFamily: "Cairo",\n              fontWeight: 600,\n            }}\n            iconType="circle"\n            iconSize={6}\n            align="right"\n            verticalAlign="top"\n            height={36}\n            formatter={(value) => (\n              <span\n                style={{\n                  color: "#CCC",\n                  fontSize: "1.125rem",\n                  fontWeight: 600,\n                  fontFamily: "Cairo",\n                }}\n              >\n                {value}\n              </span>\n            )}\n          />\n          <Bar\n            label={(e) => {\n              if(e.index === activeBarIndex){\n                setToolTipYPosition(e.height)\n              }\n              return null;\n            }}\n            barSize={10}\n            dataKey="value"\n            name={props.legendText}\n            fill="#EFC92B"\n            onMouseOver={(data) => {\n              setBarGraphData(data);\n            }}\n            onMouseLeave={(data) => {\n              setBarGraphData(data);\n            }}\n          />\n        </BarChart>\n      </ResponsiveContainer>\n    </BaseCard>\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n