chart.js 真实比例的时间分布

chart.js time scatter with true scale

已经有几个类似的帖子,但他们的解决方案要么不完整,要么不适用于现代 chart.js

问题:如何将此代码更新为具有点间真实距离的时间散点图?目前,它是一个线性图,点与点之间的距离为单位。如您所见,1 月 11 日和 1 月 8 日(3 天)之间的距离与例如 1 月 11 日和 1 月 8 日之间的距离相同。 1 月 8 日和 1 月 7 日(1 天)。

let testPoints =  [
    { x: '2017-01-06 00:00:00', y: '20' },
    { x: '2017-01-07 00:00:00', y: '17' },
    { x: '2017-01-08 00:00:00', y: '14' },
    { x: '2017-01-11 00:00:00', y: '7' },
  ]

var s1 = {
  label:'Overall activity',
  borderColor: '#33b5e5',
  data: testPoints
};

config = {
    type: 'line',
    data: { datasets: [s1] },
    options: {
        plugins: {
            title: {
                display: true,
                text: 'Overall activity plot',
                font: {
                    size: 20
                }
            }
        },
        legend: {
            display: false
        },
        scales: {
            xAxes: [{
                type: 'time',
                weight : 0,
                time: {
                    unit:'day'
                }
            }],
        }
    }
};

new Chart(ctx, config);

注意:如果我包含 moment.js v2.29.1 并使用 x: moment('2017-01-06 00:00:00'),该图根本不会绘制并出现以下错误:

Uncaught SyntaxError: Unexpected token export moment.js:5662
Uncaught ReferenceError: moment is not defined

注意:如果我只设置 config.type: 'scatter',绘图就会变成空的。

堆栈:

chart.js v3.5.1

我找到了一个简单的解决方案。 第一步是生成一个 labels 数组,其中所有日期都包含在 x 的最小值和最大值范围内。

const labels = [];
const help = moment(testPoints[0].x);

while (moment(help).isSameOrBefore(moment(testPoints[testPoints.length - 1].x))) {
  labels.push(help.format('YYYY-MM-DD HH:mm:ss'));
  help.add(1, 'day');
}

之后你可以像这样在 config.data 里面添加 labels

const config = {
  type: 'line',
  data: { 
    labels, 
    datasets: [s1]
  },

  options: {
    plugins: {
      ...
    }
  },
  ...
}

下面是一个工作示例

function generateDateLabelsFromDataset (dataset) {
  const labels = [];
  let max, min = undefined;
  for (const { x } of testPoints) { // I don't know if your dataset is ordered so i get the min and max for generate labels, if your data set is ordered min and max are the first and the last x value of your array
    if (!max || moment(x).isAfter(max)) max = moment(x);
    if (!min || moment(x).isBefore(min)) min = moment(x);
  }

  while (moment(min).isSameOrBefore(max)) {
    labels.push(min.format('YYYY-MM-DD HH:mm:ss'));
    min.add(1, 'day');
  }
  return labels;
}

const testPoints = [
  { x: '2017-01-06 00:00:00', y: '20' },
  { x: '2017-01-07 00:00:00', y: '17' },
  { x: '2017-01-08 00:00:00', y: '14' },
  { x: '2017-01-11 00:00:00', y: '7' },
];

const s1 = {
  label: 'Overall activity',
  borderColor: '#33b5e5',
  data: testPoints
};

const labels = generateDateLabelsFromDataset(testPoints)

const config = {
  type: 'line',
  data: {
    labels,
    datasets: [s1]
  },

  options: {
    plugins: {
      title: {
        display: true,
        text: 'Overall activity plot',
        font: {
          size: 20
        }
      }
    },
    legend: {
      display: false
    },
    scales: {
      xAxes: [{
        type: 'time',
        weight: 0,
        time: {
          unit: 'day'
        }
      }],
    }
  }
};

var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

<canvas id="myChart" width="200" height="200"></canvas>

新尝试

function generateDateLabelsFromDataset (dataset) {
  const labels = [];
  let max, min = undefined;
  for (const { x } of testPoints) { // I don't know if your dataset is ordered so i get the min and max for generate labels, if your data set is ordered min and max are the first and the last x value of your array
    if (!max || moment(x).isAfter(max)) max = moment(x);
    if (!min || moment(x).isBefore(min)) min = moment(x);
  }

  min.startOf('day'); // you can remove this 2 lines if you don't want the blank space before and after the segment
  max.endOf('day');

  while (moment(min).isSameOrBefore(max)) {
    labels.push(min.format('YYYY-MM-DD HH:mm:ss'));
    min.add(1, 'hour'); // here put your littler unit that you want (hour, minute, second ecc..)
  }
  return labels;
}

const testPoints = [
  { x: '2017-01-06 12:00:00', y: '20' },
  { x: '2017-01-07 00:00:00', y: '17' },
  { x: '2017-01-08 00:00:00', y: '14' },
  { x: '2017-01-11 00:00:00', y: '7' },
];

const s1 = {
  label: 'Overall activity',
  borderColor: '#33b5e5',
  data: testPoints
};

const labels = generateDateLabelsFromDataset(testPoints)

const config = {
  type: 'line',
  data: {
    labels,
    datasets: [s1]
  },

  options: {
    plugins: {
      title: {
        display: true,
        text: 'Overall activity plot',
        font: {
          size: 20
        }
      }
    },
    legend: {
      display: false
    },
    scales: {
      xAxes: [{
        type: 'time',
        weight: 0,
        time: {
          unit: 'day'
        },
        ticks: {
          callback: function(val, index) {
            // Hide the label of every 2nd dataset
            return index % 24 === 0 ? this.getLabelForValue(val) : ''; // is the hours, but if you want use minutes, you must change 24 in 24 * 60 if you want keep just the day like label
          }
        }
        
      }],
    }
  }
};

var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

<canvas id="myChart" width="200" height="200"></canvas>