最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

html - Is it possible to make the tooltip scrollable in Chartjs, react? - Stack Overflow

programmeradmin3浏览0评论

I want to make a scrollable tooltip in Chartjs because I have a lot of data to show.
I have made an external HTML tooltip referencing the official document, but I am not sure how to make it scrollable.
I have added max-height and overflow-y in tooltipEL style (like the code below), but it is not working.

 const getOrCreateTooltip = (chart: any) => {
    let tooltipEl = chart.canvas.parentNode.querySelector("div");

    if (!tooltipEl) {
      tooltipEl = document.createElement("div");
      tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
      tooltipEl.style.borderRadius = "3px";
      tooltipEl.style.color = "white";
      tooltipEl.style.opacity = 1;
      tooltipEl.style.pointerEvents = "none";
      tooltipEl.style.position = "absolute";
      tooltipEl.style.transform = "translate(-50%, 0)";
      tooltipEl.style.transition = "all .1s ease";

      const table = document.createElement("table");
      table.style.margin = "0px";

      tooltipEl.appendChild(table);
      chart.canvas.parentNode.appendChild(tooltipEl);
    }

    return tooltipEl;
  };

  const externalTooltipHandler = (context: any) => {
    // Tooltip Element
    const { chart, tooltip } = context;
    const tooltipEl = getOrCreateTooltip(chart);

    // Hide if no tooltip
    if (tooltip.opacity === 0) {
      tooltipEl.style.opacity = 0;
      return;
    }

    // Set Text
    if (tooltip.body) {
      const titleLines = tooltip.title || [];
      const bodyLines = tooltip.body.map((b: any) => b.lines);

      const tableHead = document.createElement("thead");

      titleLines.forEach((title: any) => {
        const tr = document.createElement("tr");
        tr.style.borderWidth = "0";

        const th = document.createElement("th");
        th.style.borderWidth = "0";
        const text = document.createTextNode(title);

        th.appendChild(text);
        tr.appendChild(th);
        tableHead.appendChild(tr);
      });

      const tableBody = document.createElement("tbody");
      bodyLines.forEach((body: any, i: any) => {
        const colors = tooltip.labelColors[i];

        const span = document.createElement("span");
        span.style.background = colors.backgroundColor;
        span.style.borderColor = colors.borderColor;
        span.style.borderWidth = "2px";
        span.style.marginRight = "10px";
        span.style.height = "10px";
        span.style.width = "10px";
        span.style.display = "inline-block";

        const tr = document.createElement("tr");
        tr.style.fontSize = "14px";
        tr.style.backgroundColor = "inherit";
        tr.style.borderWidth = "0";

        const td = document.createElement("td");
        td.style.borderWidth = "0";

        const text = document.createTextNode(body);

        td.appendChild(span);
        td.appendChild(text);
        tr.appendChild(td);
        tableBody.appendChild(tr);
      });

      const tableRoot = tooltipEl.querySelector("table");

      while (tableRoot.firstChild) {
        tableRoot.firstChild.remove();
      }

      tableRoot.appendChild(tableHead);
      tableRoot.appendChild(tableBody);
    }

    const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;

    tooltipEl.style.opacity = 1;
    tooltipEl.style.left = positionX + tooltip.caretX + "px";
    tooltipEl.style.top = positionY + tooltip.caretY + "px";
    tooltipEl.style.font = tooltip.options.bodyFont.string;
    tooltipEl.style.padding =
      tooltip.options.padding + "px " + tooltip.options.padding + "px";

    tooltipEl.style.maxHeight = "330px";
    tooltipEl.style.overflowY = "auto";
    tooltipEl.style.zIndex = 1000;
  };

  const options = {
    maintainAspectRatio: false,
    scales: {
      x: {
        offset: true,
        grid: {
          drawTicks: false,
          offset: true,
          color: "#00001A26",
        },
        ticks: {
          color: "rgba(0 ,0 ,0 ,0.7)",
          padding: 4,
          stepSize: 1,
          font: {
            size: 12,
          },
        },
        border: {
          dash: [2, 2],
        },
        min: -3,
        max: 3,
      },
      y: {
        beginAtZero: true,
        grid: {
          drawTicks: false,
          color: "#00001A26",
        },
        ticks: {
          color: "rgba(0 ,0 ,0 ,0.7)",
          padding: 4,
          maxTicksLimit: 5,
          font: {
            size: 12,
          },
        },
        border: {
          dash: [2, 2],
          display: false,
        },
      },
    },
    plugins: {
      tooltip: {
        enabled: false,
        external: externalTooltipHandler,
      },
    },
  };

Is it impossible to make the tooltip vertically scrollable?

I want to make a scrollable tooltip in Chartjs because I have a lot of data to show.
I have made an external HTML tooltip referencing the official document, but I am not sure how to make it scrollable.
I have added max-height and overflow-y in tooltipEL style (like the code below), but it is not working.

 const getOrCreateTooltip = (chart: any) => {
    let tooltipEl = chart.canvas.parentNode.querySelector("div");

    if (!tooltipEl) {
      tooltipEl = document.createElement("div");
      tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
      tooltipEl.style.borderRadius = "3px";
      tooltipEl.style.color = "white";
      tooltipEl.style.opacity = 1;
      tooltipEl.style.pointerEvents = "none";
      tooltipEl.style.position = "absolute";
      tooltipEl.style.transform = "translate(-50%, 0)";
      tooltipEl.style.transition = "all .1s ease";

      const table = document.createElement("table");
      table.style.margin = "0px";

      tooltipEl.appendChild(table);
      chart.canvas.parentNode.appendChild(tooltipEl);
    }

    return tooltipEl;
  };

  const externalTooltipHandler = (context: any) => {
    // Tooltip Element
    const { chart, tooltip } = context;
    const tooltipEl = getOrCreateTooltip(chart);

    // Hide if no tooltip
    if (tooltip.opacity === 0) {
      tooltipEl.style.opacity = 0;
      return;
    }

    // Set Text
    if (tooltip.body) {
      const titleLines = tooltip.title || [];
      const bodyLines = tooltip.body.map((b: any) => b.lines);

      const tableHead = document.createElement("thead");

      titleLines.forEach((title: any) => {
        const tr = document.createElement("tr");
        tr.style.borderWidth = "0";

        const th = document.createElement("th");
        th.style.borderWidth = "0";
        const text = document.createTextNode(title);

        th.appendChild(text);
        tr.appendChild(th);
        tableHead.appendChild(tr);
      });

      const tableBody = document.createElement("tbody");
      bodyLines.forEach((body: any, i: any) => {
        const colors = tooltip.labelColors[i];

        const span = document.createElement("span");
        span.style.background = colors.backgroundColor;
        span.style.borderColor = colors.borderColor;
        span.style.borderWidth = "2px";
        span.style.marginRight = "10px";
        span.style.height = "10px";
        span.style.width = "10px";
        span.style.display = "inline-block";

        const tr = document.createElement("tr");
        tr.style.fontSize = "14px";
        tr.style.backgroundColor = "inherit";
        tr.style.borderWidth = "0";

        const td = document.createElement("td");
        td.style.borderWidth = "0";

        const text = document.createTextNode(body);

        td.appendChild(span);
        td.appendChild(text);
        tr.appendChild(td);
        tableBody.appendChild(tr);
      });

      const tableRoot = tooltipEl.querySelector("table");

      while (tableRoot.firstChild) {
        tableRoot.firstChild.remove();
      }

      tableRoot.appendChild(tableHead);
      tableRoot.appendChild(tableBody);
    }

    const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;

    tooltipEl.style.opacity = 1;
    tooltipEl.style.left = positionX + tooltip.caretX + "px";
    tooltipEl.style.top = positionY + tooltip.caretY + "px";
    tooltipEl.style.font = tooltip.options.bodyFont.string;
    tooltipEl.style.padding =
      tooltip.options.padding + "px " + tooltip.options.padding + "px";

    tooltipEl.style.maxHeight = "330px";
    tooltipEl.style.overflowY = "auto";
    tooltipEl.style.zIndex = 1000;
  };

  const options = {
    maintainAspectRatio: false,
    scales: {
      x: {
        offset: true,
        grid: {
          drawTicks: false,
          offset: true,
          color: "#00001A26",
        },
        ticks: {
          color: "rgba(0 ,0 ,0 ,0.7)",
          padding: 4,
          stepSize: 1,
          font: {
            size: 12,
          },
        },
        border: {
          dash: [2, 2],
        },
        min: -3,
        max: 3,
      },
      y: {
        beginAtZero: true,
        grid: {
          drawTicks: false,
          color: "#00001A26",
        },
        ticks: {
          color: "rgba(0 ,0 ,0 ,0.7)",
          padding: 4,
          maxTicksLimit: 5,
          font: {
            size: 12,
          },
        },
        border: {
          dash: [2, 2],
          display: false,
        },
      },
    },
    plugins: {
      tooltip: {
        enabled: false,
        external: externalTooltipHandler,
      },
    },
  };

Is it impossible to make the tooltip vertically scrollable?

Share Improve this question edited Mar 19 at 1:10 Ji Yeon Kim asked Mar 19 at 1:10 Ji Yeon KimJi Yeon Kim 111 bronze badge
Add a comment  | 

1 Answer 1

Reset to default 0

Yes, it's possible. You can create a scrollable tooltip :

import React, { useEffect, useRef, useState } from "react";

const ScrollableTooltipDemo = () => {
  const chartRef = useRef(null);
  const containerRef = useRef(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Load Chart.js script dynamically
    const loadChartJs = async () => {
      try {
        setLoading(true);

        // Create script element to load Chart.js from CDN
        const script = document.createElement("script");
        script.src =
          "https://cdnjs.cloudflare/ajax/libs/Chart.js/3.9.1/chart.min.js";
        script.async = true;

        // Set up load handler
        script.onload = () => {
          // Initialize chart once Chart.js is loaded
          initializeChart();
          setLoading(false);
        };

        // Set up error handler
        script.onerror = () => {
          setError("Failed to load Chart.js library");
          setLoading(false);
        };

        // Add script to document
        document.body.appendChild(script);

        // Cleanup function
        return () => {
          document.body.removeChild(script);
        };
      } catch (err) {
        setError(`Error loading Chart.js: ${err.message}`);
        setLoading(false);
      }
    };

    const initializeChart = () => {
      // Ensure Chart.js is available
      if (typeof window.Chart === "undefined") {
        setError("Chart.js failed to load properly");
        return;
      }

      const ctx = chartRef.current.getContext("2d");

      // Generate sample data
      const months = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
      ];
      const generateRandomData = (count, min, max) =>
        Array.from({ length: count }, () =>
          Math.floor(Math.random() * (max - min + 1) + min)
        );

      // Define tooltip plugin
      const tooltipPlugin = {
        id: "tooltipInteraction",
        beforeEvent(chart, args) {
          const event = args.event;
          if (event.type === "mouseout") {
            // Check if we're still in the tooltip
            const tooltipEl = chart.canvas.parentNode.querySelector(
              "div.chartjs-tooltip"
            );
            if (tooltipEl && tooltipEl._isHovered) {
              // Prevent default mouseout behavior
              args.replay = false;
            }
          }
        },
      };

      // External tooltip handler
      const getOrCreateTooltip = (chart) => {
        let tooltipEl = chart.canvas.parentNode.querySelector(
          "div.chartjs-tooltip"
        );

        if (!tooltipEl) {
          tooltipEl = document.createElement("div");
          tooltipEl.className = "chartjs-tooltip";
          tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
          tooltipEl.style.borderRadius = "3px";
          tooltipEl.style.color = "white";
          tooltipEl.style.opacity = 1;
          tooltipEl.style.pointerEvents = "auto"; // Enable pointer events
          tooltipEl.style.position = "absolute";
          tooltipEl.style.transform = "translate(-50%, 0)";
          tooltipEl.style.transition = "all .1s ease";
          tooltipEl.style.maxHeight = "150px"; // Set a reasonable max height
          tooltipEl.style.overflowY = "auto"; // Enable vertical scrolling
          tooltipEl.style.width = "220px"; // Fixed width for better scrolling
          tooltipEl.style.zIndex = 1000;
          tooltipEl.style.boxShadow = "0 2px 5px rgba(0,0,0,0.25)";
          tooltipEl.style.padding = "10px";

          const table = document.createElement("table");
          table.style.margin = "0px";
          table.style.width = "100%";

          tooltipEl.appendChild(table);
          chart.canvas.parentNode.appendChild(tooltipEl);

          // Add event listeners for tooltip interaction
          tooltipEl.addEventListener("mouseenter", () => {
            tooltipEl._isHovered = true;
          });

          tooltipEl.addEventListener("mouseleave", () => {
            tooltipEl._isHovered = false;
            // Hide the tooltip when mouse leaves (with delay)
            setTimeout(() => {
              if (!tooltipEl._isHovered) {
                tooltipEl.style.opacity = 0;
              }
            }, 500);
          });
        }

        return tooltipEl;
      };

      const externalTooltipHandler = (context) => {
        const { chart, tooltip } = context;
        const tooltipEl = getOrCreateTooltip(chart);

        // Check if we should hide the tooltip
        if (tooltip.opacity === 0 && !tooltipEl._isHovered) {
          tooltipEl.style.opacity = 0;
          return;
        }

        // Set Text
        if (tooltip.body) {
          const titleLines = tooltip.title || [];
          const bodyLines = tooltip.body.map((b) => b.lines);

          const tableHead = document.createElement("thead");

          titleLines.forEach((title) => {
            const tr = document.createElement("tr");
            tr.style.borderWidth = "0";

            const th = document.createElement("th");
            th.style.borderWidth = "0";
            th.style.padding = "5px";
            const text = document.createTextNode(title);

            th.appendChild(text);
            tr.appendChild(th);
            tableHead.appendChild(tr);
          });

          const tableBody = document.createElement("tbody");
          bodyLines.forEach((body, i) => {
            const colors = tooltip.labelColors[i];

            const span = document.createElement("span");
            span.style.background = colors.backgroundColor;
            span.style.borderColor = colors.borderColor;
            span.style.borderWidth = "2px";
            span.style.marginRight = "10px";
            span.style.height = "10px";
            span.style.width = "10px";
            span.style.display = "inline-block";

            const tr = document.createElement("tr");
            tr.style.fontSize = "14px";
            tr.style.backgroundColor = "inherit";
            tr.style.borderWidth = "0";

            const td = document.createElement("td");
            td.style.borderWidth = "0";
            td.style.padding = "5px";

            const text = document.createTextNode(body);

            td.appendChild(span);
            td.appendChild(text);
            tr.appendChild(td);
            tableBody.appendChild(tr);
          });

          const tableRoot = tooltipEl.querySelector("table");

          // Clear previous content
          while (tableRoot.firstChild) {
            tableRoot.firstChild.remove();
          }

          tableRoot.appendChild(tableHead);
          tableRoot.appendChild(tableBody);
        }

        // Position the tooltip
        const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;

        tooltipEl.style.opacity = 1;

        // Position adjustments to avoid tooltip being cut off
        const tooltipWidth = tooltipEl.offsetWidth || 220;
        const horizontalAdjustment =
          tooltip.caretX + tooltipWidth / 2 > chart.width
            ? -(tooltip.caretX + tooltipWidth / 2 - chart.width)
            : 0;

        tooltipEl.style.left =
          positionX + tooltip.caretX + horizontalAdjustment + "px";
        tooltipEl.style.top = positionY + tooltip.caretY + "px";
        tooltipEl.style.font = tooltip.options.bodyFont.string;
      };

      // Create datasets with multiple series
      const data = {
        labels: months,
        datasets: [
          {
            label: "Dataset 1",
            data: generateRandomData(12, 10, 100),
            borderColor: "rgb(75, 192, 192)",
            backgroundColor: "rgba(75, 192, 192, 0.2)",
            tension: 0.3,
          },
          {
            label: "Dataset 2",
            data: generateRandomData(12, 20, 90),
            borderColor: "rgb(255, 99, 132)",
            backgroundColor: "rgba(255, 99, 132, 0.2)",
            tension: 0.3,
          },
          {
            label: "Dataset 3",
            data: generateRandomData(12, 30, 80),
            borderColor: "rgb(54, 162, 235)",
            backgroundColor: "rgba(54, 162, 235, 0.2)",
            tension: 0.3,
          },
          {
            label: "Dataset 4",
            data: generateRandomData(12, 5, 70),
            borderColor: "rgb(255, 159, 64)",
            backgroundColor: "rgba(255, 159, 64, 0.2)",
            tension: 0.3,
          },
          {
            label: "Dataset 5",
            data: generateRandomData(12, 15, 85),
            borderColor: "rgb(153, 102, 255)",
            backgroundColor: "rgba(153, 102, 255, 0.2)",
            tension: 0.3,
          },
        ],
      };

      try {
        // Register custom plugin
        window.Chart.register(tooltipPlugin);

        // Ensure canvas is correctly sized
        const containerWidth = containerRef.current.offsetWidth;
        chartRef.current.width = containerWidth;
        chartRef.current.height = 300;

        // Chart configuration
        const chartInstance = new window.Chart(ctx, {
          type: "line",
          data: data,
          options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              legend: {
                position: "top",
              },
              title: {
                display: true,
                text: "Chart.js Line Chart with Scrollable Tooltip",
              },
              tooltip: {
                enabled: false,
                external: externalTooltipHandler,
                mode: "index",
                intersect: false,
                events: [
                  "mousemove",
                  "mouseout",
                  "click",
                  "touchstart",
                  "touchmove",
                ],
              },
            },
            scales: {
              x: {
                grid: {
                  drawTicks: false,
                  color: "#00001A26",
                },
                ticks: {
                  color: "rgba(0, 0, 0, 0.7)",
                  padding: 4,
                  font: {
                    size: 12,
                  },
                },
                border: {
                  dash: [2, 2],
                },
              },
              y: {
                beginAtZero: true,
                grid: {
                  drawTicks: false,
                  color: "#00001A26",
                },
                ticks: {
                  color: "rgba(0, 0, 0, 0.7)",
                  padding: 4,
                  font: {
                    size: 12,
                  },
                },
                border: {
                  dash: [2, 2],
                },
              },
            },
            interaction: {
              mode: "index",
              intersect: false,
            },
            hover: {
              mode: "index",
              intersect: false,
            },
          },
        });

        // Store chart instance for cleanup
        chartRef.current.chartInstance = chartInstance;
      } catch (err) {
        setError(`Error initializing chart: ${err.message}`);
      }
    };

    loadChartJs();

    // Cleanup on unmount
    return () => {
      if (chartRef.current && chartRef.current.chartInstance) {
        chartRef.current.chartInstance.destroy();
      }
    };
  }, []);

  return (
    <div className="w-full p-4">
      <div className="flex flex-col items-center">
        <div className="w-full max-w-4xl p-4 bg-white rounded-lg shadow-md">
          <h2 className="text-xl font-bold mb-4 text-center">
            Chart.js Scrollable Tooltip Demo
          </h2>
          <p className="mb-4 text-gray-700 text-center">
            Hover over data points to see the scrollable tooltip. You can scroll
            inside the tooltip without it disappearing.
          </p>
          <div
            className="h-64 mb-4 relative"
            ref={containerRef}
            style={{ position: "relative", width: "100%", height: "300px" }}
          >
            {loading && (
              <div className="absolute inset-0 flex items-center justify-center">
                <div className="text-gray-500">Loading chart...</div>
              </div>
            )}
            {error && (
              <div className="absolute inset-0 flex items-center justify-center">
                <div className="text-red-500">{error}</div>
              </div>
            )}
            <canvas
              ref={chartRef}
              style={{ width: "100%", height: "100%" }}
            ></canvas>
          </div>
          <div className="text-sm text-gray-600 mt-4">
            <p>
              <strong>Instructions:</strong>
            </p>
            <ul className="list-disc pl-6">
              <li>Hover over points on the chart to see the tooltip</li>
              <li>
                When the tooltip appears, you can move your cursor to it and
                scroll
              </li>
              <li>The tooltip should remain visible while scrolling</li>
              <li>
                The tooltip will disappear after you move your cursor away
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
};

export default ScrollableTooltipDemo;
发布评论

评论列表(0)

  1. 暂无评论