如何在 plotly.js 中按周对值进行分组

How to group values by week in plotly.js

下面是一个时间序列条形图,在 plotly.js 中有一个范围选择器。

在其中,我试图找出如何按周对值进行分组,但似乎无法实现。 plotly.js 中是否有设置在更改时间范围选择时按周对这些进行分组?我似乎无法弄清楚这是否可能。

这是他们提供的主要文档页面,其中我尝试了尽可能多的设置以实现此目的,但无法弄清楚。

var days = (function(start,count){
  var days = [];
  var MSday = 1000 * 60 * 60 * 24;
  for(var i = 0; i < count; i++){
    days.push(new Date(+start + i*MSday));
  }
  return days;
})(new Date(2018,0,1),100);

function vals(){
  var vals = [];
  for(var i = 0; i < 100; i++){
    vals.push((Math.random() * 2 * i) | 0);
  }
  return vals;
}

var selectorOptions = {
  buttons: [{
    step: 'month',
    stepmode: 'backward',
    count: 1,
    label: '1m'
  }, {
    step: 'month',
    stepmode: 'backward',
    count: 6,
    label: '6m'
  }, {
    step: 'year',
    stepmode: 'todate',
    count: 1,
    label: 'YTD'
  }, {
    step: 'year',
    stepmode: 'backward',
    count: 1,
    label: '1y'
  }, {
    step: 'all',
  }],
};

var trace1 = {
  x: days,
  y: vals(),
  type: 'bar',
  name: 'Trace 1'
};

var trace2 = {
  x: days,
  y: vals(),
  type: 'bar',
  name: 'Trace 2'
};

var data = [trace1, trace2];

var layout = {
  title: 'Bar Demo',
  barmode: 'group',
  xaxis: {
    rangeselector: selectorOptions
  }
};

Plotly.newPlot('myDiv', data, layout);
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv"><!-- Plotly chart will be drawn inside this DIV --></div>

如何在图表上按周而不是按天来选择 6 个月的组?

显然这不是内置的。如果它是内置的,或者在某个时候内置,请在评论或其他答案中指出。

我能够确定可行的唯一选择是使用 .on('plotly_relayout', function () { 挂钩重新布局事件,从范围选择器按钮中获取参数(这似乎有限,只有从和到日期,如果有更好的方法来确定起源请也让我知道,我会在这里更新),然后大致基于此按周对日期进行分类并调整图中的 x 和 y 值。

这只是作为概念验证的基本实现。在生产中使用它需要重构此代码以在设计和页面实现方面使用现有数据结构。

这里发生了很多事情。基本上,它将遍历日期集以创建将保存每周数据的星期日分箱(请注意,它仍然缺少显示更新以显示它是从开始日期算起的一周)。一旦有了 bin,它就会对每个 bin 范围内的日期求和。然后它使用 restyle 替换数据集。如果选择的范围不是6m那么它会使用备份数据的切片,因为plotly修改数组的位置,结果如果没有备份副本,每次备份都会覆盖一个副本被使用了。

请参阅下面的工作演示。

function sum(array){
 return array.reduce(function(sum,curr){
   return sum + curr;
  },0);
};
Date.MSday = 1000 * 60 * 60 * 24;
Date.prototype.floor = function(){
 return new Date(this.getFullYear(),this.getMonth(),this.getDate());
}
Date.prototype.addDays = function(days){
 var time = +this - +this.floor();
  var addedDays = new Date(+this.floor() + Date.MSday*days);
  return new Date(+addedDays + time);
}

function weeksFromDates(datesArray, valsArray){
 var lastDay = datesArray[datesArray.length -1];
 var firstDay = datesArray[0];
  var dayOfWeek = firstDay.getDay();
  var firstSunday = firstDay.addDays(-dayOfWeek);
  var sundays = [];
  var currentSunday = firstSunday;
  while(currentSunday < lastDay){
   sundays.push(currentSunday);
    currentSunday = currentSunday.addDays(7);
  }
  currentSunday = currentSunday.addDays(7);
  sundays.push(currentSunday);
  
  var valSets = [];
  
  var n = 0;
  for(var i = 1; i < sundays.length; i++){
   var last = sundays[i-1];
    var next = sundays[i];
    var theseVals = [];
    for(; n < datesArray.length && last <= datesArray[n] && next > datesArray[n]; n++){
      theseVals.push(valsArray[n]);
    }
   valSets.push(sum(theseVals));
  }
  sundays.pop();
  return {x: sundays, y: valSets};
}

var MSday = 1000 * 60 * 60 * 24;
var days = (function(start,count){
  var days = [];
  for(var i = 0; i < count; i++){
    days.push(new Date(+start + i*MSday));
  }
  return days;
})(new Date(2018,0,1),100);

function vals(){
  var vals = [];
  for(var i = 0; i < 100; i++){
    vals.push((Math.random() * 2 * i) | 0);
  }
  return vals;
}

var selectorOptions = {
  buttons: [{
    step: 'month',
    stepmode: 'backward',
    count: 1,
    label: '1m'
  }, {
    step: 'month',
    stepmode: 'backward',
    count: 6,
    label: '6m'
  }, {
    step: 'year',
    stepmode: 'todate',
    count: 1,
    label: 'YTD'
  }, {
    step: 'year',
    stepmode: 'backward',
    count: 1,
    label: '1y'
  }, {
    step: 'all',
  }],
};

var trace1 = {
  x: days,
  y: vals(),
  type: 'bar',
  name: 'Trace 1',
  orientation: 'v'
};

var trace2 = {
  x: days,
  y: vals(),
  type: 'bar',
  name: 'Trace 2',
  orientation: 'v'
};

var data = [trace1, trace2];
var dataBackup = $.extend(true,{},data);

var layout = {
  title: 'Bar Demo',
  barmode: 'group',
  xaxis: {
    rangeselector: selectorOptions
  }
};

Plotly.newPlot('myDiv', data, layout);
$('#myDiv').on('plotly_relayout', function () {
    var lower = new Date(arguments[1]['xaxis.range[0]']);
    var upper = new Date(arguments[1]['xaxis.range[1]']);
    var dayRange = (+upper - +lower) / MSday;
    if( dayRange < 190 && dayRange > 170 ){
        //6m
        for(var n = 0; n < data.length; n++){
        var weekly = weeksFromDates(dataBackup[n].x,dataBackup[n].y);
        Plotly.restyle('myDiv',{x:[weekly.x],y: [weekly.y]},n);
        }
    }else{
      for(var n = 0; n < data.length; n++){
        Plotly.restyle('myDiv',{x:[dataBackup[n].x.slice()],y: [dataBackup[n].y.slice()]},n);
        }
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv"><!-- Plotly chart will be drawn inside this DIV --></div>

天哪!有一个更简单的选择...

使用 7 天:

    step: 'day',
    stepmode: 'backward',
    count: 7,
    label: '1w'