首页 > 解决方案 > 简单的 D3.js 条形图:组“g”位置和尺寸搞砸了

问题描述

我是 D3.js 的新手,试图制作一个简单的条形图。这是我的 POC:

<!DOCTYPE html>
<html>

<head></head>

<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.1/d3.js"></script>

<script type="text/javascript">
    dataSet = [
        { 'year': 1851, 'data': -5.0 },
        { 'year': 1861, 'data': -4.5 },
        { 'year': 1871, 'data': -3.2 },
        { 'year': 1881, 'data': -2.8 },
        { 'year': 1891, 'data': -1.7 },
        { 'year': 1901, 'data': -1.0 },
        { 'year': 1911, 'data': 1.2 },
        { 'year': 1921, 'data': 2.5 },
        { 'year': 1931, 'data': 3.0 },
        { 'year': 1941, 'data': 3.9 },
        { 'year': 1951, 'data': 4.7 },
    ];
    console.log(dataSet);
    const widthSvg = 1024;
    const heightSvg = 768;

    const svg = d3.select('body')
        .append('svg')
        .attr('width', widthSvg)
        .attr('height', heightSvg);

    const outerAxisPadding = 70;
    const yAxisWidth = 20;
    const xAxisHeight = 20;

    const margins = {
        left: outerAxisPadding + yAxisWidth,
        top: outerAxisPadding,
        bottom: outerAxisPadding + xAxisHeight,
        right: outerAxisPadding
    };

    const graphArea = {
        left: margins.left,
        top: margins.top,
        right: widthSvg - margins.right,
        bottom: heightSvg - margins.bottom,
        width: 0,
        height: 0
    };
    graphArea.width = graphArea.right - graphArea.left;
    graphArea.height = graphArea.bottom - graphArea.top;

    const dataGroup = svg.append('g')
        .attr('transform', 'translate(' + graphArea.left + ',' + graphArea.top + ')')
        .attr('width', graphArea.width - 10)
        .attr('height', graphArea.height - 10);

    const xScale = d3.scaleLinear()
        .domain([
            d3.min(
                dataSet.map(d => d.year)
            ) - 15,
            d3.max(
                dataSet.map(d => d.year)
            ) + 15
        ])
        .range([0, graphArea.width]);

    const yScaleForAxis = d3.scaleLinear()
        .domain([
            d3.min(dataSet.map(d => d.data)),
            d3.max(dataSet.map(d => d.data))
        ])
        .nice()
        .range([graphArea.height, 0]);

    const yScaleForValues = d3.scaleLinear()
        .domain([
            d3.min(dataSet.map(d => Math.abs(d.data))),
            d3.max(dataSet.map(d => Math.abs(d.data)))
        ])
        .nice()
        .range([0, graphArea.height / 2]);

    // for debug:
    dataSet.forEach(d => console.log(d.year + ' ' + d.data + ' ' + yScaleForValues(d.data)));

    const xAxis = d3.axisBottom(xScale);
    const yAxis = d3.axisLeft(yScaleForAxis);

    // add yAxis nodes
    svg.append('g')
        .attr('transform', 'translate(' + outerAxisPadding + ',' + graphArea.top + ')')
        .call(yAxis);

    // add xAxis nodes
    svg.append('g')
        .attr('transform', 'translate(' + graphArea.left + ',' + (heightSvg - outerAxisPadding) + ')')
        .call(xAxis);

    dataGroup.selectAll('rect')
        .data(dataSet)
        .enter()
        .append('rect')
        .text((d, i) => d.year + ' ' + d.data) // just for information / debugging
        .attr('fill', 'blue')
        .attr('x', (d, i) => xScale(d.year))
        .attr('y', (d, i) => {
            if (d.data > 0) {
                return graphArea.height - yScaleForValues(0) - yScaleForValues(d.data);
            } else {
                return graphArea.height - yScaleForValues(0);
            }
        })
        .attr('width', '15')
        .attr('height', (d, i) => yScaleForValues(Math.abs(d.data)));
</script>
</body>
</html>

我不知道它是否结构良好,或良好的做法。这里的目标是将dataSet数组注入到rects中,years字段是x轴,data字段是y轴。

但是,如您所见,问题在于应该包含矩形的dataGroup的位置和尺寸错误。

谢谢

标签: javascriptd3.js

解决方案


终于让它工作了,完全不同的版本:

<!DOCTYPE html>
<html>

<head></head>

<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.1/d3.js"></script>

    <script type="text/javascript">
        dataSet = [
            { 'year': 1831, 'data': -6.9 },
            { 'year': 1841, 'data': -5.7 },
            { 'year': 1851, 'data': -5.0 },
            { 'year': 1861, 'data': -4.5 },
            { 'year': 1871, 'data': -3.2 },
            { 'year': 1881, 'data': -2.8 },
            { 'year': 1891, 'data': -1.7 },
            { 'year': 1901, 'data': -1.0 },
            { 'year': 1911, 'data': 1.2 },
            { 'year': 1921, 'data': 2.5 },
            { 'year': 1931, 'data': 3.0 },
            { 'year': 1941, 'data': 3.9 },
            { 'year': 1951, 'data': 4.7 },
        ];
        console.log(dataSet);
        const widthSvg = 1024;
        const heightSvg = 768;

        const svg = d3.select('body')
            .append('svg')
            .attr('width', widthSvg)
            .attr('height', heightSvg);

        const outerAxisPadding = 70;
        const yAxisWidth = 20;
        const xAxisHeight = 20;

        const margins = {
            left: outerAxisPadding + yAxisWidth,
            top: outerAxisPadding,
            bottom: outerAxisPadding + xAxisHeight,
            right: outerAxisPadding
        };

        const graphArea = {
            left: margins.left,
            top: margins.top,
            right: widthSvg - margins.right,
            bottom: heightSvg - margins.bottom,
            width: 0,
            height: 0
        };
        graphArea.width = graphArea.right - graphArea.left;
        graphArea.height = graphArea.bottom - graphArea.top;

        const dataGroup = svg.append('g')
            .attr('transform', 'translate(' + graphArea.left + ',' + graphArea.top + ')')
            .attr('width', graphArea.width - 10)
            .attr('height', graphArea.height - 10);

        const xScale = d3.scaleLinear()
            .domain([
                d3.min(
                    dataSet.map(d => d.year)
                ) - 15,
                d3.max(
                    dataSet.map(d => d.year)
                ) + 15
            ])
            .range([0, graphArea.width]);

        const yScaleForAxis = d3.scaleLinear()
            .domain([
                d3.min(dataSet.map(d => d.data)),
                d3.max(dataSet.map(d => d.data))
            ])
            .nice()
            .range([graphArea.height, 0]);

        const yScaleForValues = d3.scaleLinear()
            .domain([
                d3.min(dataSet.map(d => d.data)),
                d3.max(dataSet.map(d => d.data))
            ])
            .nice()
            .range([0, graphArea.height]);

        // for debug:
        dataSet.forEach(d => console.log(d.year + ' ' + d.data + ' ' + yScaleForValues(d.data)));

        const xAxis = d3.axisBottom(xScale);
        const yAxis = d3.axisLeft(yScaleForAxis);

        // add yAxis nodes
        svg.append('g')
            .attr('transform', 'translate(' + outerAxisPadding + ',' + graphArea.top + ')')
            .call(yAxis);

        // add gridlines
        svg.append('g')
            .attr('transform', 'translate(' + margins.left + ',' + graphArea.top + ')')
            .call(yAxis.tickFormat(data => '').tickSize(-graphArea.width));

        // add xAxis nodes
        svg.append('g')
            .attr('transform', 'translate(' + graphArea.left + ',' + (heightSvg - outerAxisPadding) + ')')
            .call(xAxis);

        dataGroup.selectAll('rect')
            .data(dataSet)
            .enter()
            .append('rect')
            .text((d, i) => d.year + ' ' + d.data) // just for information / debugging
            .attr('fill', 'blue')
            .attr('x', (d, i) => xScale(d.year))
            .attr('y', (d, i) => {
                if (d.data > 0) {
                    return graphArea.height - yScaleForValues(0) - (yScaleForValues(d.data) - yScaleForValues(0)); // can shorten maths :p
                } else {
                    return graphArea.height - yScaleForValues(0);
                }
            })
            .attr('width', '15')
            .attr('height', (d, i) => {
                if (d.data > 0) {
                    return yScaleForValues(d.data) - yScaleForValues(0);
                } else {
                    return yScaleForValues(0) - yScaleForValues(d.data);
                }
            });
    </script>
</body>

</html><!DOCTYPE html>
<html>

<head></head>

<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.1/d3.js"></script>

    <script type="text/javascript">
        dataSet = [
            { 'year': 1831, 'data': -6.9 },
            { 'year': 1841, 'data': -5.7 },
            { 'year': 1851, 'data': -5.0 },
            { 'year': 1861, 'data': -4.5 },
            { 'year': 1871, 'data': -3.2 },
            { 'year': 1881, 'data': -2.8 },
            { 'year': 1891, 'data': -1.7 },
            { 'year': 1901, 'data': -1.0 },
            { 'year': 1911, 'data': 1.2 },
            { 'year': 1921, 'data': 2.5 },
            { 'year': 1931, 'data': 3.0 },
            { 'year': 1941, 'data': 3.9 },
            { 'year': 1951, 'data': 4.7 },
        ];
        console.log(dataSet);
        const widthSvg = 1024;
        const heightSvg = 768;

        const svg = d3.select('body')
            .append('svg')
            .attr('width', widthSvg)
            .attr('height', heightSvg);

        const outerAxisPadding = 70;
        const yAxisWidth = 20;
        const xAxisHeight = 20;

        const margins = {
            left: outerAxisPadding + yAxisWidth,
            top: outerAxisPadding,
            bottom: outerAxisPadding + xAxisHeight,
            right: outerAxisPadding
        };

        const graphArea = {
            left: margins.left,
            top: margins.top,
            right: widthSvg - margins.right,
            bottom: heightSvg - margins.bottom,
            width: 0,
            height: 0
        };
        graphArea.width = graphArea.right - graphArea.left;
        graphArea.height = graphArea.bottom - graphArea.top;

        const dataGroup = svg.append('g')
            .attr('transform', 'translate(' + graphArea.left + ',' + graphArea.top + ')')
            .attr('width', graphArea.width - 10)
            .attr('height', graphArea.height - 10);

        const xScale = d3.scaleLinear()
            .domain([
                d3.min(
                    dataSet.map(d => d.year)
                ) - 15,
                d3.max(
                    dataSet.map(d => d.year)
                ) + 15
            ])
            .range([0, graphArea.width]);

        const yScaleForAxis = d3.scaleLinear()
            .domain([
                d3.min(dataSet.map(d => d.data)),
                d3.max(dataSet.map(d => d.data))
            ])
            .nice()
            .range([graphArea.height, 0]);

        const yScaleForValues = d3.scaleLinear()
            .domain([
                d3.min(dataSet.map(d => d.data)),
                d3.max(dataSet.map(d => d.data))
            ])
            .nice()
            .range([0, graphArea.height]);

        // for debug:
        dataSet.forEach(d => console.log(d.year + ' ' + d.data + ' ' + yScaleForValues(d.data)));

        const xAxis = d3.axisBottom(xScale);
        const yAxis = d3.axisLeft(yScaleForAxis);

        // add yAxis nodes
        svg.append('g')
            .attr('transform', 'translate(' + outerAxisPadding + ',' + graphArea.top + ')')
            .call(yAxis);

        // add gridlines
        svg.append('g')
            .attr('transform', 'translate(' + margins.left + ',' + graphArea.top + ')')
            .call(yAxis.tickFormat(data => '').tickSize(-graphArea.width));

        // add xAxis nodes
        svg.append('g')
            .attr('transform', 'translate(' + graphArea.left + ',' + (heightSvg - outerAxisPadding) + ')')
            .call(xAxis);

        dataGroup.selectAll('rect')
            .data(dataSet)
            .enter()
            .append('rect')
            .text((d, i) => d.year + ' ' + d.data) // just for information / debugging
            .attr('fill', 'blue')
            .attr('x', (d, i) => xScale(d.year))
            .attr('y', (d, i) => {
                if (d.data > 0) {
                    return graphArea.height - yScaleForValues(0) - (yScaleForValues(d.data) - yScaleForValues(0)); // can shorten maths :p
                } else {
                    return graphArea.height - yScaleForValues(0);
                }
            })
            .attr('width', '15')
            .attr('height', (d, i) => {
                if (d.data > 0) {
                    return yScaleForValues(d.data) - yScaleForValues(0);
                } else {
                    return yScaleForValues(0) - yScaleForValues(d.data);
                }
            });
    </script>
</body>

</html>

我仍然对任何评论/批评感兴趣。


推荐阅读