javascript - d3 - 如何根据周格式化 xAxis 刻度

标签 javascript d3.js

我有一个 d3 时间刻度图表。目前,轴刻度为每个数据对象呈现一个日期。例如,数据的范围可以是 1 天的数据、1 个月内 2 周的数据、5 个月的数据甚至更多。

理想情况下,我们希望显示带有周数的刻度,基于数据 - 不是年或月的周数,如下所示:xAxis .tickFormat(d3.time.format('%W')) 例如,如果数据从 7 月 15 日开始,第一个刻度将表示“第 1 周”,然后是“第 2 周”,等等。

如何使用基于时间的 d3 折线图实现这样的轴刻度?我是 d3 的新手,几乎不知道如何实现这一目标。

我也在使用 moment.js,所以在 xAxis.tickFormat 中,我尝试使用一个带有一些逻辑的函数,该函数根据日期返回不同的值,但这看起来很脆弱并且不是“d3 方式”。还尝试使用自定义时间格式化程序 as seen here .

或者,我们可以使用更简单的刻度 - 仅使用 d3.time.format(%d-%b) 显示月份和/或日期,但是会有重复的刻度值,例如 'Feb ...Feb..Feb..Feb...Feb..Mar..Mar..Mar..`。有没有办法避免出现重复值?

我已经尝试限制滴答声的数量,但这并没有像预期的那样起作用。例如,如果我有 xAxis.ticks(5),则出现 3 个刻度;如果我有 xAxis.ticks(2),情况也一样。如果 ticks 定义为 1,则只显示 1 个 ticks。这是怎么回事?

任何帮助将不胜感激!下面的代码包含 2 个数据集示例

<!DOCTYPE html>

<body>

<style>
path { fill: #CCC; }
.line { fill: none; stroke: #000; stroke-width: 5px;}
</style>

<div id="chart-container">
    <svg id="chart"></svg>
</div>

<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>

<script>

    var now = moment();
    var chartData = [
        { timestamp: moment(now).subtract(27, 'days').format('DD-MMM-YY'), value: 40 },
        { timestamp: moment(now).subtract(25, 'days').format('DD-MMM-YY'), value: 36 },
        { timestamp: moment(now).subtract(24, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(21, 'days').format('DD-MMM-YY'), value: 35 },
        { timestamp: moment(now).subtract(20, 'days').format('DD-MMM-YY'), value: 35 },
        { timestamp: moment(now).subtract(18, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(17, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(16, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(15, 'days').format('DD-MMM-YY'), value: 32 },
        { timestamp: moment(now).subtract(13, 'days').format('DD-MMM-YY'), value: 35 },
        { timestamp: moment(now).subtract(11, 'days').format('DD-MMM-YY'), value: 31 },
        { timestamp: moment(now).subtract(10, 'days').format('DD-MMM-YY'), value: 28 },
        { timestamp: moment(now).subtract(9, 'days').format('DD-MMM-YY'), value: 32 },
        { timestamp: moment(now).subtract(8, 'days').format('DD-MMM-YY'), value: 30 },
        { timestamp: moment(now).subtract(7, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(6, 'days').format('DD-MMM-YY'), value: 36 }
    ];


    //data could have a shorter date range of eg, 1 or 2 weeks
    //ideally we want to still display 'week 1, 2, 3, 4' etc in the axis.
    //alternatively display dates instead
    // var chartData = [
    //     { timestamp: moment(now).subtract(27, 'days').format('DD-MMM-YY'), value: 40 },
    //     { timestamp: moment(now).subtract(25, 'days').format('DD-MMM-YY'), value: 36 },
    //     { timestamp: moment(now).subtract(24, 'days').format('DD-MMM-YY'), value: 33 },
    //     { timestamp: moment(now).subtract(21, 'days').format('DD-MMM-YY'), value: 35 },
    //     { timestamp: moment(now).subtract(20, 'days').format('DD-MMM-YY'), value: 35 }
    // ];

    let lastObj = chartData[chartData.length - 1];
    let lastObjTimestamp =  lastObj.timestamp;
    let lastAndNow = moment(lastObjTimestamp).diff(now, 'days');
    console.log('difference between last entry ' + lastObjTimestamp  + ' and today: ' + lastAndNow);

    var chartWrapperDomId = 'chart-container';
    var chartDomId = 'chart';
    var chartWrapperWidth = document.getElementById(chartWrapperDomId).clientWidth;
    var margin = 40;
    var width = chartWrapperWidth - margin;
    var height = 500 - margin * 2;

    var xMin = d3.time.format('%d-%b-%y').parse(chartData[0].timestamp);
    var xMax = d3.time.format('%d-%b-%y').parse(chartData[chartData.length-1].timestamp);

    //set the scale for the x axis
    var xScale = d3.time.scale();
    xScale.domain([xMin, xMax]);
    xScale.range([0, width]);

    var yScale = d3.scale.linear()
        .range([height, 0])
        .nice();

    console.log('no5 ', chartData[5].timestamp)

    var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom')
        .tickFormat(d3.time.format('%d-%b'));
        //.tickFormat(d3.time.format('%b'))
        //tickFormat(d3.time.format('%W'));
        //.ticks(5);

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');

    var line = d3.svg.line()
        .x(function(d) {
            return xScale(d.timestamp);
        })
        .y(function(d) {
            return yScale(d.value);
        });

    var svg = d3.select('#' + chartDomId)
        .attr('width', width + margin * 2)
        .attr('height', height + margin * 2)
      .append('g')
        .attr('transform', 'translate(' + margin + ',' + margin + ')');

    chartData.forEach(function(d) {
        d.timestamp = d3.time.format('%d-%b-%y').parse(d.timestamp);
        d.value = +d.value;
    });

    yScale.domain(d3.extent(chartData, function(d) {
        return d.value;
    }));

    svg.append('g')
        .attr('class', 'axis x-axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis);

    svg.append('g')
        .attr('class', 'axis y-axis')
        .call(yAxis)
    .append('text')
        .attr('transform', 'rotate(-90)')
        .attr('y', 6)
        .attr('dy', '.71em')
        .style('text-anchor', 'end');

    svg.append('path')
        .datum(chartData)
        .attr('class', 'line')
        .attr('d', line);

</script>

</body>

最佳答案

您需要从数据中找到第一个工作日(例如星期三)并根据它设置刻度。

可以使用以下代码实现:

var weekday = new Array(7);
weekday[0]=  'sunday';
weekday[1] = 'monday';
weekday[2] = 'tuesday';
weekday[3] = 'wednesday';
weekday[4] = 'thursday';
weekday[5] = 'friday';
weekday[6] = 'saturday';

var startDay = weekday[new Date(chartData[0].timestamp).getDay()];

var week = 1;
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient('bottom')
    .tickFormat(function() { return 'week ' + week++ })
    .ticks(d3.time[startDay]);

<!DOCTYPE html>

<body>

<style>
path { fill: #CCC; }
.line { fill: none; stroke: #000; stroke-width: 5px;}
</style>

<div id="chart-container">
    <svg id="chart"></svg>
</div>

<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>

<script>

    var now = moment();
    var chartData = [
        { timestamp: moment(now).subtract(27, 'days').format('DD-MMM-YY'), value: 40 },
        { timestamp: moment(now).subtract(25, 'days').format('DD-MMM-YY'), value: 36 },
        { timestamp: moment(now).subtract(24, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(21, 'days').format('DD-MMM-YY'), value: 35 },
        { timestamp: moment(now).subtract(20, 'days').format('DD-MMM-YY'), value: 35 },
        { timestamp: moment(now).subtract(18, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(17, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(16, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(15, 'days').format('DD-MMM-YY'), value: 32 },
        { timestamp: moment(now).subtract(13, 'days').format('DD-MMM-YY'), value: 35 },
        { timestamp: moment(now).subtract(11, 'days').format('DD-MMM-YY'), value: 31 },
        { timestamp: moment(now).subtract(10, 'days').format('DD-MMM-YY'), value: 28 },
        { timestamp: moment(now).subtract(9, 'days').format('DD-MMM-YY'), value: 32 },
        { timestamp: moment(now).subtract(8, 'days').format('DD-MMM-YY'), value: 30 },
        { timestamp: moment(now).subtract(7, 'days').format('DD-MMM-YY'), value: 33 },
        { timestamp: moment(now).subtract(6, 'days').format('DD-MMM-YY'), value: 36 }
    ];


    //data could have a shorter date range of eg, 1 or 2 weeks
    //ideally we want to still display 'week 1, 2, 3, 4' etc in the axis.
    //alternatively display dates instead
    // var chartData = [
    //     { timestamp: moment(now).subtract(27, 'days').format('DD-MMM-YY'), value: 40 },
    //     { timestamp: moment(now).subtract(25, 'days').format('DD-MMM-YY'), value: 36 },
    //     { timestamp: moment(now).subtract(24, 'days').format('DD-MMM-YY'), value: 33 },
    //     { timestamp: moment(now).subtract(21, 'days').format('DD-MMM-YY'), value: 35 },
    //     { timestamp: moment(now).subtract(20, 'days').format('DD-MMM-YY'), value: 35 }
    // ];

    let lastObj = chartData[chartData.length - 1];
    let lastObjTimestamp =  lastObj.timestamp;
    let lastAndNow = moment(lastObjTimestamp).diff(now, 'days');
    console.log('difference between last entry ' + lastObjTimestamp  + ' and today: ' + lastAndNow);

    var chartWrapperDomId = 'chart-container';
    var chartDomId = 'chart';
    var chartWrapperWidth = document.getElementById(chartWrapperDomId).clientWidth;
    var margin = 40;
    var width = chartWrapperWidth - margin;
    var height = 500 - margin * 2;

    var xMin = d3.time.format('%d-%b-%y').parse(chartData[0].timestamp);
    var xMax = d3.time.format('%d-%b-%y').parse(chartData[chartData.length-1].timestamp);

    //set the scale for the x axis
    var xScale = d3.time.scale();
    xScale.domain([xMin, xMax]);
    xScale.range([0, width]);

    var yScale = d3.scale.linear()
        .range([height, 0])
        .nice();


    console.log('no5 ', chartData[5].timestamp)

    var weekday = new Array(7);
    weekday[0]=  'sunday';
    weekday[1] = 'monday';
    weekday[2] = 'tuesday';
    weekday[3] = 'wednesday';
    weekday[4] = 'thursday';
    weekday[5] = 'friday';
    weekday[6] = 'saturday';

var startDay = weekday[new Date(chartData[0].timestamp).getDay()];

    var week = 1;
    var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom')
        //.tickFormat(d3.time.format('%d-%b'));
        //.tickFormat(d3.time.format('%b'))
        //tickFormat(d3.time.format('%W'));
        .tickFormat(function() { return 'week ' + week++ })
        .ticks(d3.time[startDay]);

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');

    var line = d3.svg.line()
        .x(function(d) {
            return xScale(d.timestamp);
        })
        .y(function(d) {
            return yScale(d.value);
        });

    var svg = d3.select('#' + chartDomId)
        .attr('width', width + margin * 2)
        .attr('height', height + margin * 2)
      .append('g')
        .attr('transform', 'translate(' + margin + ',' + margin + ')');

    chartData.forEach(function(d) {
        d.timestamp = d3.time.format('%d-%b-%y').parse(d.timestamp);
        d.value = +d.value;
    });

    yScale.domain(d3.extent(chartData, function(d) {
        return d.value;
    }));

    svg.append('g')
        .attr('class', 'axis x-axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis);

    svg.append('g')
        .attr('class', 'axis y-axis')
        .call(yAxis)
    .append('text')
        .attr('transform', 'rotate(-90)')
        .attr('y', 6)
        .attr('dy', '.71em')
        .style('text-anchor', 'end');

    svg.append('path')
        .datum(chartData)
        .attr('class', 'line')
        .attr('d', line);

</script>

</body>

关于javascript - d3 - 如何根据周格式化 xAxis 刻度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36128327/

相关文章:

javascript - jQuery:为什么下面不分配美元 '$'?

javascript - 如何创建和缓存尚不存在的 DOM 元素?

Javascript 无法在 IE 11 的 onblur 函数中访问键盘事件,但在 IE 9 中则不能

javascript - 如何修复 d3 折线图的时间刻度?

javascript - D3 通过引用强制布局绑定(bind)不同步

javascript - 初始缩放在 d3 v4 中不起作用

javascript - 如何在 D3.js 中输入数组?

javascript - 带有 InfoWindows 的 Google map Javascript

javascript - 如何在每次调用时为 setInterval 生成新的延迟?

javascript - 如何创建水平图例?