【问题标题】:How to align x axis with bars respectively in Combo Chart (Bars and Lines)?如何在组合图表(条形图和线条形)中分别将 x 轴与条形对齐?
【发布时间】:2019-04-18 21:20:38
【问题描述】:

我有一个基于 D3.js 的组合/条形图和折线图。 x 轴域包含最小和最大日期,条形基于值。但最后一个柱(矩形)在图表之外。我可以通过强制(手动)将其引入,但它不会反映数据。

var data = [
    {
        fcst_valid_local: "2018-11-13T14:00:00-0600",
        pop: 20,
        rh: 67,
        temp: 38,
        wspd: 7
    },
    {
        fcst_valid_local: "2018-11-14T15:00:00-0600",
        pop: 15,
        rh: 50,
        temp: 39,
        wspd: 8
    },
    {
        fcst_valid_local: "2018-11-15T16:00:00-0600",
        pop: 10,
        rh: 90,
        temp: 40,
        wspd: 9
    }
];

// Margins, width and height.
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 500 - margin.left - margin.right,
    height = 200 - margin.top - margin.bottom;

// Date parsing.
const parseDate = d3.timeParse("%Y-%m-%dT%H:%M:%S%Z");
data.forEach(function (d) {
    d.date = parseDate(d.fcst_valid_local);
});

// Set scale domains.
var x = d3.scaleTime().range([0, width])
    .domain(d3.extent(data, function (d) {
        return d.date;
    }));

var y0 = d3.scaleLinear().range([height, 0]).domain([0, 100]);
const y1 = d3.scaleLinear()
    .range([height, 0])
    .domain([0, d3.max(data, (d) => d.pop)]);

// Construct our SVG object.
const svg = d3.select('svg')
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append('g').attr('class', 'container')
    .attr("transform",
        "translate(" + margin.left + "," + margin.top + ")");

// Set x, y-left and y-right axis.
var xAxis = d3.axisBottom(x)
    .ticks(d3.timeDay.every(1))
    // .tickFormat(d3.timeFormat('%b %d, %H:%M'))
    .tickSize(0).tickPadding(10);
var y0Axis = d3.axisLeft(y0)
    .ticks(5).tickSize(0);
var y1Axis = d3.axisRight(y1).ticks(5).tickSize(0);

svg.append("g")
    .attr("class", "x-axis axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);
svg.append("g")
    .attr("class", "y-axis axis")
    .attr("transform", "translate(" + 0 + ", 0)")
    .call(y0Axis);
svg.append("g")
    .attr("class", "y-axis axis")
    .attr("transform", "translate(" + width + ", 0)")
    .call(y1Axis);

// Draw bars.
var bars = svg.selectAll(".precips")
    .data(data);

bars.exit().remove();

bars.enter().append("rect")
    .attr("class", "precip")
    .attr("width", width / data.length - 50)
    .attr("x", function (d) {
        return x(d.date);
    })
    .attr("y", height)
    .transition().duration(1000)
    .attr("y", function (d) {
        return y0(d.pop);
    })
    .attr("height", function (d) {
        return height - y0(d.pop);
    });

const lineRH = d3.line()
    .x((d) => x(d['date']))
    .y(d => y0(d['rh']));

svg.append('path')
    .datum(data)
    .attr('class', 'line')
    .attr('fill', 'none')
    .attr('stroke', 'red')
    .attr('stroke-linejoin', 'round')
    .attr('stroke-linecap', 'round')
    .attr('stroke-width', 1.5)
    .attr('d', lineRH);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    添加一个比最后一项稍晚一点的虚拟数据项

    这里是硬编码的,但您可以根据最后一项的日期动态添加它

    var data = [
        {
            fcst_valid_local: "2018-11-13T14:00:00-0600",
            pop: 20,
            rh: 67,
            temp: 38,
            wspd: 7
        },
        {
            fcst_valid_local: "2018-11-14T15:00:00-0600",
            pop: 15,
            rh: 50,
            temp: 39,
            wspd: 8
        },
        {
            fcst_valid_local: "2018-11-15T16:00:00-0600",
            pop: 10,
            rh: 90,
            temp: 40,
            wspd: 9
        },
        {
            fcst_valid_local: "2018-11-16T01:00:00-0600"
        }
    ];
    
    // Margins, width and height.
    var margin = {top: 20, right: 20, bottom: 30, left: 50},
        width = 500 - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom;
    
    // Date parsing.
    const parseDate = d3.timeParse("%Y-%m-%dT%H:%M:%S%Z");
    data.forEach(function (d) {
        d.date = parseDate(d.fcst_valid_local);
    });
    
    // Set scale domains.
    var x = d3.scaleTime().range([0, width])
        .domain(d3.extent(data, function (d) {
            return d.date;
        }));
    
    var y0 = d3.scaleLinear().range([height, 0]).domain([0, 100]);
    const y1 = d3.scaleLinear()
        .range([height, 0])
        .domain([0, d3.max(data, (d) => d.pop)]);
    
    // Construct our SVG object.
    const svg = d3.select('svg')
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append('g').attr('class', 'container')
        .attr("transform",
            "translate(" + margin.left + "," + margin.top + ")");
    
    // Set x, y-left and y-right axis.
    var xAxis = d3.axisBottom(x)
        .ticks(d3.timeDay.every(1))
        // .tickFormat(d3.timeFormat('%b %d, %H:%M'))
        .tickSize(0).tickPadding(10);
    var y0Axis = d3.axisLeft(y0)
        .ticks(5).tickSize(0);
    var y1Axis = d3.axisRight(y1).ticks(5).tickSize(0);
    
    svg.append("g")
        .attr("class", "x-axis axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
    svg.append("g")
        .attr("class", "y-axis axis")
        .attr("transform", "translate(" + 0 + ", 0)")
        .call(y0Axis);
    svg.append("g")
        .attr("class", "y-axis axis")
        .attr("transform", "translate(" + width + ", 0)")
        .call(y1Axis);
    
    // Draw bars.
    var bars = svg.selectAll(".precips")
        .data(data);
    
    bars.exit().remove();
    
    bars.enter().append("rect")
        .attr("class", "precip")
        .attr("width", width / data.length - 50)
        .attr("x", function (d) {
            return x(d.date);
        })
        .attr("y", height)
        .transition().duration(1000)
        .attr("y", function (d) {
            return y0(d.pop);
        })
        .attr("height", function (d) {
            return height - y0(d.pop);
        });
    
    const lineRH = d3.line()
        .x((d) => x(d['date']))
        .y(d => y0(d['rh']));
    
    svg.append('path')
        .datum(data)
        .attr('class', 'line')
        .attr('fill', 'none')
        .attr('stroke', 'red')
        .attr('stroke-linejoin', 'round')
        .attr('stroke-linecap', 'round')
        .attr('stroke-width', 1.5)
        .attr('d', lineRH);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg></svg>

    【讨论】:

    • 如果我有来自 API 的数据,这将无济于事。这是一种解决方法,但正在寻找实用的解决方案。谢谢
    • @MaihanNijat:就像我说过要根据数据集中的最后一个值动态添加它
    • 谢谢,它会在直线(红色)和右侧 y 轴之间产生间隙。此外,x 轴未与矩形对齐。是什么造成了这一切?
    • @MaihanNijat:你不能让红线接触右 y 轴 并且有一个从最后一个日期开始且不超过右 y 的条形-轴
    【解决方案2】:

    虽然答案已被接受,但我想告诉您,您不必操纵 data(因为它也可能从 API 中获取),但您可以玩转 @987654324 @ 因为这是关于在此处设置正确的

    1. 尝试使用d3 time_nice 对时间尺度域进行四舍五入
    2. 使用d3 time 更改日期的方法(这里有很多)

    以下是使用上述第二种方法并设置 x 域的示例:

    var x = d3.scaleTime().range([0, width])
    .domain([d3.min(data, function (d) {
        return d.date;
    }), d3.timeDay.offset(d3.max(data, function (d) { return d.date; }), 1)]);
    

    解释:这会将数据中的最大日期偏移 1 天,因此新的 x.domain() 将显示为:

    (2) [Tue Nov 13 2018 15:00:00 GMT-0500 (Eastern Standard Time), Fri Nov 16 2018 17:00:00 GMT-0500 (Eastern Standard Time)]
    

    生成如下图表:

    var data = [
        {
            fcst_valid_local: "2018-11-13T14:00:00-0600",
            pop: 20,
            rh: 67,
            temp: 38,
            wspd: 7
        },
        {
            fcst_valid_local: "2018-11-14T15:00:00-0600",
            pop: 15,
            rh: 50,
            temp: 39,
            wspd: 8
        },
        {
            fcst_valid_local: "2018-11-15T16:00:00-0600",
            pop: 10,
            rh: 90,
            temp: 40,
            wspd: 9
        }
    ];
    
    // Margins, width and height.
    var margin = {top: 20, right: 20, bottom: 30, left: 50},
        width = 500 - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom;
    
    // Date parsing.
    const parseDate = d3.timeParse("%Y-%m-%dT%H:%M:%S%Z");
    data.forEach(function (d) {
        d.date = parseDate(d.fcst_valid_local);
    });
    
    // Set scale domains.
    var x = d3.scaleTime().range([0, width])
        .domain([d3.min(data, function (d) {
            return d.date;
        }), d3.timeDay.offset(d3.max(data, function (d) { return d.date; }), 1)]);
    
    var y0 = d3.scaleLinear().range([height, 0]).domain([0, 100]);
    const y1 = d3.scaleLinear()
        .range([height, 0])
        .domain([0, d3.max(data, (d) => d.pop)]);
    //console.log(x.domain());
    // Construct our SVG object.
    const svg = d3.select('svg')
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append('g').attr('class', 'container')
        .attr("transform",
            "translate(" + margin.left + "," + margin.top + ")");
    
    // Set x, y-left and y-right axis.
    var xAxis = d3.axisBottom(x)
        .ticks(d3.timeDay.every(1))
        // .tickFormat(d3.timeFormat('%b %d, %H:%M'))
        .tickSize(0).tickPadding(10);
    var y0Axis = d3.axisLeft(y0)
        .ticks(5).tickSize(0);
    var y1Axis = d3.axisRight(y1).ticks(5).tickSize(0);
    
    svg.append("g")
        .attr("class", "x-axis axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
    svg.append("g")
        .attr("class", "y-axis axis")
        .attr("transform", "translate(" + 0 + ", 0)")
        .call(y0Axis);
    svg.append("g")
        .attr("class", "y-axis axis")
        .attr("transform", "translate(" + width + ", 0)")
        .call(y1Axis);
    
    // Draw bars.
    var bars = svg.selectAll(".precips")
        .data(data);
    
    bars.exit().remove();
    
    bars.enter().append("rect")
        .attr("class", "precip")
        .attr("width", width / data.length - 50)
        .attr("x", function (d) {
            return x(d.date);
        })
        .attr("y", height)
        .transition().duration(1000)
        .attr("y", function (d) {
            return y0(d.pop);
        })
        .attr("height", function (d) {
            return height - y0(d.pop);
        });
    
    const lineRH = d3.line()
        .x((d) => x(d['date']) + (width / data.length - 50)/2)
        .y(d => y0(d['rh']));
    
    svg.append('path')
        .datum(data)
        .attr('class', 'line')
        .attr('fill', 'none')
        .attr('stroke', 'red')
        .attr('stroke-linejoin', 'round')
        .attr('stroke-linecap', 'round')
        .attr('stroke-width', 1.5)
        .attr('d', lineRH);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg></svg>

    我还尝试了.nice(),其中一个有趣的部分是使用.nice() 内的 d3 时间间隔。随意玩弄这些,如果您有任何问题,请告诉我。

    另外,我通过行生成器 fn 中的 barwidth/2 来抵消 line(路径)。

    d3.line()
    .x((d) => x(d['date']) + (width / data.length - 50)/2)
    

    希望这也有帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-15
      • 2018-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-18
      • 1970-01-01
      • 2021-01-31
      相关资源
      最近更新 更多