【问题标题】:how to make responsive labels using D3如何使用 D3 制作响应式标签
【发布时间】:2021-07-05 08:34:57
【问题描述】:

我正在使用 D3 charts,通过反应,我创建了条形图并使其具有响应性。 当我调整浏览器的大小时,栏会相应地占用宽度。

问题

我想要实现的一件事是动态标签,比如当我调整页面大小时,标签的长度有点大,所以它比其他的要长

点赞

所以我想克服这个问题,我搜索了很多,发现了一个例子,当它全屏呈现时,它看起来像

当我将浏览器调整为小屏幕时!it looks like this]3

以上为is the reference I found

所以我想实现但没有任何想法。

我做了什么

import React, { useRef, useEffect, useState } from 'react';
import { select, axisBottom, axisRight, scaleLinear, scaleBand } from 'd3';
import ResizeObserver from 'resize-observer-polyfill';

const useResizeObserver = (ref) => {  // doing this for resizing
    const [dimensions, setDimensions] = useState(null);
    useEffect(() => {
        const observeTarget = ref.current;
        const resizeObserver = new ResizeObserver((entries) => {
            entries.forEach((entry) => {
                setDimensions(entry.contentRect);
            });
        });
        resizeObserver.observe(observeTarget);
        return () => {
            resizeObserver.unobserve(observeTarget);
        };
    }, [ref]);
    return dimensions;
};

function BarChart({ data }) {
    const svgRef = useRef();
    const wrapperRef = useRef();
    const dimensions = useResizeObserver(wrapperRef);

    // will be called initially and on every data change
    useEffect(() => {
        const svg = select(svgRef.current);

        if (!dimensions) return;

        // scales
        const xScale = scaleBand()
            .domain(data.map((value, index) => index + '2021+01+06'))
            .range([0, dimensions.width]) // change
            .padding(0.5);

        const yScale = scaleLinear()
            .domain([0, 150]) // todo
            .range([dimensions.height, 0]); // change

        // create x-axis

        const xAxis = axisBottom(xScale).ticks(data.length);
        svg
            .select('.x-axis')
            .style('transform', `translateY(${dimensions.height}px)`)
            .call(xAxis);

        // create y-axis
        const yAxis = axisRight(yScale);
        svg
            .select('.y-axis')
            .style('transform', `translateX(${dimensions.width}px)`)
            .call(yAxis);

        // draw the bars
        svg
            .selectAll('.bar')
            .data(data)
            .join('rect')
            .attr('class', 'bar')
            .style('transform', 'scale(1, -1)')
            .attr('x', (value, index) => xScale(index + '2021+01+06'))
            .attr('y', -dimensions.height)
            .attr('width', xScale.bandwidth())

            .transition()
            .attr('fill', '#015e89')
            .attr('height', (value) => dimensions.height - yScale(value));
    }, [data, dimensions]);

    return (
        <div ref={wrapperRef} style={{ marginBottom: '2rem' }}>
            <svg ref={svgRef}>
                <g className="x-axis" />
                <g className="y-axis" />
            </svg>
        </div>
    );
}

export default BarChart;

这里是my codesandbox

【问题讨论】:

    标签: javascript reactjs d3.js react-hooks


    【解决方案1】:

    一种常见的解决方案是旋转标签,使它们在没有宽度时不会重叠:

        svg
          .select(".x-axis")
          .selectAll("text")
          .attr("y", 0)
          .attr("x", 0)
          .attr("dy", "2em")
          .attr("transform", "rotate(330)")
          .style("text-anchor", "end");
    

    这种方法的好处是易于实施,只需稍作调整即可覆盖 90% 的情况(当您知道标签中的数据时)。
    Codesandbox - rotate labels

    编辑

    我已尝试执行示例中的操作,它需要稍作调整才能工作,因为目前您正在根据索引生成标签。

    这部分负责跳过 X 刻度上的值,我从您的示例中获取它,它按预期工作。确保正确设置刻度宽度:

        const tickWidth = 80;
        const width = xScale.range()[1];
        const tickN = Math.floor(width / tickWidth);
        const keepEveryNth = Math.ceil(xScale.domain().length / tickN);
    
        const xScaleDomain = xScale
          .domain()
          .filter((_, i) => i % keepEveryNth === 0);
        xScale.domain(xScaleDomain);
    

    但它会产生一个视觉错误,因为您根据比例定位条(.attr("x", (value, index) =&gt; xScale(index + "2021+01+06"))),但比例不再具有该标签,因此它返回未定义。这导致该栏位于x = 0

    我已经尝试过滤掉条形的数据以仅包括在比例尺上找到的条形:

        const diplayData = data.filter((_, i) => {
          return xScaleDomain.includes(i + "2021+01+06");
        });
    

    但这不行,标签是用索引构建的,索引是一种不可靠的识别方式。

    玩玩这个沙盒,我相信你可以从这里得到你需要的东西。
    Codesandbox - dynamic ticks

    编辑 2

    在绘制条形图时,我可以使用以下行来处理任何数据:

          .attr("x", (_, i) => xScale(xScale.domain()[i]))
    

    我还冒昧地添加了动态 yScale 大小:

        const yScale = scaleLinear()
          .domain([0, 1.1 * Math.max(...data.map(({ consumo }) => consumo))])
    

    编辑 3

    我想我已经实现了你所描述的。我为条形添加了单独的比例,因此始终显示所有条形。另一方面,标签会随着可用空间的减少而消失。我不确定为同一轴设置 2 个刻度是否是一个好习惯。
    确保两个刻度保持相同的填充,否则会出现奇怪的偏移。

    这是包含示例数据的最终版本,它应该适用于任何数据,只要您正确映射项目:
    Codesandbox - Final version

    【讨论】:

    • 这将是一种解决方法,我想按照该示例的方式进行操作,因为我有很多数据要一次性显示。
    • 我已经编辑了答案,如果您将使用真实数据并删除所有 i + ...index + ... 它应该可以工作。
    • 嘿,实际上我想用动态(JSON)数据本身来做这个,但不知道,[ { anno: 2014, consumo: 300, }, { anno: 2015, consumo: 290, }, { anno: 2016, consumo: 295, }, { anno: 2017, consumo: 287, }, { anno: 2018, consumo: 282, }, { anno: 2019, consumo: 195, }, ]我有这个数据你能用这个创建图表吗,它会对我有很大帮助
    • 当我调整大小时,条形也会重叠,我想是因为我在创建时没有 json 数据。
    • 查看第三个也是最后一个编辑。如果这仍然不是您想要的,我很乐意看看您会接受的解决方案,因为我的 D3 在这一点上已经很生锈了。
    猜你喜欢
    • 2017-02-12
    • 2022-01-11
    • 1970-01-01
    • 1970-01-01
    • 2013-11-23
    • 2014-01-20
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    相关资源
    最近更新 更多