在 d3 中设置 bar 值

Setting bar value in d3

我正在尝试设置条形图中每个单独条形内部的数量值,如下图所示:

不幸的是,我尝试过的代码将百分比悬停在一个非常奇怪的地方,我不确定我可以做些什么来达到预期的效果。

这是我的代码:

import React, { useEffect, useRef, useState } from 'react';
import * as d3 from 'd3';
import './BarChart.css';

const dataSet = [
    { category: '1', quantity: 15 },
    { category: '2', quantity: 10 },
    { category: '3', quantity: 50 },
    { category: '4', quantity: 30 },
    { category: '4', quantity: 75 },
    { category: '5', quantity: 5 }
];

const BarChartTest = () => {
    const d3Chart = useRef();
    const [dimensions, setDimensions] = useState({
        width: window.innerWidth,
        height: window.innerHeight
    });
    const update = useRef(false);

    useEffect(() => {
        // Listen for any resize event update
        window.addEventListener('resize', () => {
            setDimensions({
                width: window.innerWidth,
                height: window.innerHeight
            });

            // if resize, remove the previous chart
            if (update.current) {
                d3.selectAll('g').remove();
            } else {
                update.current = true;
            }
        });

        DrawChart(dataSet, dimensions);
    }, [dimensions]);

    const margin = { top: 50, right: 30, bottom: 30, left: 60 };

    const DrawChart = (data, dimensions) => {
        const chartWidth = parseInt(d3.select('#d3RenewalChart').style('width')) - margin.left - margin.right;
        const chartHeight = parseInt(d3.select('#d3RenewalChart').style('height')) - margin.top - margin.bottom;

        const colors = ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f', '#bf5b17', '#666666'];

        const svg = d3
            .select(d3Chart.current)
            .attr('width', chartWidth + margin.left + margin.right)
            .attr('height', chartHeight + margin.top + margin.bottom);
        const x = d3
            .scaleBand()
            .domain(d3.range(data.length))
            .range([margin.left, chartWidth + margin.right])
            .padding(0.1);

        svg.append('g')
            .attr('transform', 'translate(0,' + chartHeight + ')')
            .call(
                d3
                    .axisBottom(x)
                    .tickFormat((i) => data[i].category)
                    .tickSizeOuter(0)
            );

        const max = d3.max(data, function (d) {
            return d.quantity;
        });

        const y = d3.scaleLinear().domain([0, 100]).range([chartHeight, margin.top]);

        svg.append('g')
            .attr('transform', 'translate(' + margin.left + ',0)')
            .call(d3.axisLeft(y).tickFormat((d) => d + '%'));

        svg.append('g')
            .attr('fill', function (d, i, j) {
                return colors[i];
            })
            .selectAll('rect')
            .data(data)
            .join('rect')
            .attr('x', (d, i) => x(i))
            .attr('y', (d) => y(d.quantity))
            .attr('height', (d) => y(0) - y(d.quantity))
            .attr('width', x.bandwidth())
            .attr('fill', function (d, i) {
                return colors[i];
            })
            .append('text')
            .text(function (d) {
                return d.quantity;
            })
            .on('click', (d) => {
                location.replace('https://www.google.com');
            });

        svg.selectAll('.text')
            .data(data)
            .enter()
            .append('text')
            // .attr('text-anchor', 'middle')
            .attr('fill', 'green')
            .attr('class', 'label')
            .attr('x', function (d) {
                return x(d.quantity);
            })
            .attr('y', function (d) {
                return y(d.quantity) - 20;
            })
            .attr('dy', '0')
            .text(function (d) {
                return d.quantity + '%';
            })
            .attr('x', function (d, i) {
                console.log(i * (chartWidth / data.length));
                return i * (chartWidth / data.length);
            })
            .attr('y', function (d) {
                console.log(chartHeight - d.quantity * 4);
                return chartHeight - d.quantity * 4;
            });
    };

    return (
        <div id="d3RenewalChart">
            <svg ref={d3Chart}></svg>
        </div>
    );
};

export default BarChartTest;

这是我 codesandbox 的 link。

提供的 Codesandbox 不包含任何 React 代码。

将上面的内容复制粘贴到组件代码中并从 App.js 调用它表明调整 window 的大小会导致问题,因为行 svg.selectAll(".text") 附加了一个新副本每次渲染(重新调整大小)。

Here is the original code in a working Codesandbox.

A refactored version of that code is in this updated Codesandbox.

解法:

除了 .append() 调用将 .text 元素附加到 svg 而不被删除之外,上面的代码似乎设置了 xy 属性与 .attr() 两次;删除附加代码并更改一些值使得可以将条形标签定位在大概正确的位置。

这是重构后的版本:

// create labels
    svg
      .append("g")
      .attr("fill", "black")
      .attr("text-anchor", "end")
      .style("font", "24px sans-serif")
      .selectAll("text")
      .data(data)
      .enter()
      .append("text")
      .attr("class", "label");

// position labels
    svg
      .selectAll(".label")
      .data(data)
      .attr("x", (d, index) => x(index) + x.bandwidth() / 2 + 24)
      .text((d) => d.quantity + "%")
// to exclude the animation, remove these two lines
      .transition()
      .delay((d, i) => i * 20)
//
      .attr("y", (d) => y(d.quantity) + 22);

另外,这里建议使用对比度较高的颜色。浅色背景上的白色文本可能不可见,并且不会为每个人提供无障碍体验。 Here's the refactored Codesandbox again.

希望这对您有所帮助! ✌️