chartjs spanGap未正确跨越的堆叠线

sulc1iza  于 2023-05-17  发布在  Chart.js
关注(0)|答案(1)|浏览(147)

我试图绘制一个折线图作为堆叠,因为有一些丢失的数据-我认为spanGaps将是一个很好的选择-但它看起来像这样的作品,如果它的“顶”线,但如果差距存在(即。data: [20, undefined, 21, 22, 25, 26, 24, 28, 30, 25, 22],),则会导致顶部的堆叠出现问题。

这是一个bug,还是有办法解决这个问题(除了尝试基于已知值创建临时虚拟值等)?

var config_working = {
  type: 'line',

  data: {
    labels: ['01 May 2023', '02 May 2023', '03 May 2023', '04 May 2023', '05 May 2023', '06 May 2023', '07 May 2023', '08 May 2023', '09 May 2023', '10 May 2023'],

    datasets: [{
      label: 'Full History',
      borderColor: '#3182BD',
      backgroundColor: '#3182BD',
      data: [20, 15, 21, 22, 25, 26, 24, 28, 30, 25, 22],
      // Count is 11
      fill: true

    },{
      label: 'Gappy data',
      borderColor: '#2c6145',
      backgroundColor: '#2c6145',
      data: [15, 14, 12, undefined, undefined, undefined, undefined, 12, 10, 14, 15],
      // Count is 11
      fill: true

    }]
  },

  options: {
    type: 'line',
    spanGaps: true,
    responsive: true,
    maintainAspectRatio: false,
    scales: {
      y: {
        stacked: true
      }
    }
  }
};

var config_problem = {
  type: 'line',

  data: {
    labels: ['01 May 2023', '02 May 2023', '03 May 2023', '04 May 2023', '05 May 2023', '06 May 2023', '07 May 2023', '08 May 2023', '09 May 2023', '10 May 2023'],

    datasets: [{
      label: 'History with one gap',
      borderColor: '#3182BD',
      backgroundColor: '#3182BD',
      data: [20, undefined, 21, 22, 25, 26, 24, 28, 30, 25, 22],
      // Count is 11
      fill: true

    },{
      label: 'Gappy data',
      borderColor: '#2c6145',
      backgroundColor: '#2c6145',
      data: [15, 14, 12, undefined, undefined, undefined, undefined, 12, 10, 14, 15],
      // Count is 11
      fill: true

    }]
  },

  options: {
    type: 'line',
    spanGaps: true,
    responsive: true,
    maintainAspectRatio: false,
    scales: {
      y: {
        stacked: true
      }
    }
  }
};

window.onload = function() {
  var ctx = document.getElementById('cvs-working').getContext('2d');
  window.myLine = new Chart(ctx, config_working);
  
  var ctx = document.getElementById('cvs-problem').getContext('2d');
  window.myLine = new Chart(ctx, config_problem);
};
h1 {
border-top:solid 1px black;
}
<script type='text/JavaScript' src='https://cdn.jsdelivr.net/npm/chart.js'></script>

  <h1>
  Working(?)...
  </h1>
<div style='height:300px'>
  <canvas id='cvs-working'></canvas>
</div>

<h1>
Problem...
</h1>
<div style='height:300px'>
  <canvas id='cvs-problem'></canvas>
</div>

(注意,tbh,我不确定“工作?......”一个也在工作-因为它似乎没有跨越蓝色系列差距)
一个jsfiddle也可以用来玩... https://jsfiddle.net/Abeeee/s07fkh69/65/

fgw7neuy

fgw7neuy1#

首先,我发现第一个图表如预期的那样工作,第二行所在的值正是它们应该的值:[35, 29, 33,----,40,40,39,37]。如果将beginAtZero: true添加到y轴上,可能会更容易看到(参见下面的示例)。
至于不工作的版本,这似乎是默认行为,我在其他库中看到过(例如echarts,参见this SO post)。如果我想得更多,我会得出这样的结论:这是有原因的,我不会再称之为bug了:

  • 对于spanGaps,我们所做的就是 * 忽略 * 一个非数字值,在一个间隙上绘制一个线段,因为它是 * 缺失 * 的。
  • 我们所期望的是,非数字值将被可以通过插值计算的新值 * 替换 *。这比spanGaps应该做的要多。

尽管如此,插值行为仍然可以(并且不是非常复杂)在chartjs代码中实现。
它也可以在一定程度上在用户领域中实现。下面是对LineController的扩展,我称之为LineControllerWithGapInterpolation,其中idline_with_gap_interpolation(用作图表或数据集的类型)。
它有一些局限性:它只适用于直线(没有样条插值,tension选项被忽略),并且它需要定义所有的x值(因此我将第11天添加到您的示例中)。

//import {Chart, registerables, Legend, LineController} from "./chart.js";
//Chart.register(...registerables);

// for umd script: (remove if import above is used)
const LineController = Chart.LineController;

class LineControllerWithGapInterpolation extends LineController{
    static id = 'line_with_gap_interpolation';

    initialize(){
        super.initialize();
        this.options.tension = 0;
    }

    parse(start, count){
        const { _cachedMeta: meta } = this;
        const { _stacked  } = meta;
        super.parse(start, count);
        if(_stacked) {
            const parsed = meta._parsed;
            let lastX = null, lastY = null, allOK = true, changed = false;
            const parsedWithInterp = parsed.map(
                function(o, i){
                    if(!allOK){
                        return o;
                    }
                    const o2 = {x:o.x, y: o.y};
                    if(Number.isFinite(o.y)){
                        lastX = o.x;
                        lastY = o.y;
                        if(!Number.isFinite(lastX)){
                            allOK = false;
                        }
                    }
                    else{
                        let nextX = null, nextY = null;
                        for(let j = i + 1; j < parsed.length; j++){
                            if(Number.isFinite(parsed[j].y)){
                                nextX = parsed[j].x;
                                nextY = parsed[j].y;
                                if(!Number.isFinite(nextX)){
                                    allOK = false;
                                }
                            }
                        }
                        //interpolation
                        o2.y = (nextY - lastY) / (nextX - lastX) * (o.x - lastX) + lastY;
                        changed = true;
                    }
                    return o2;
                }
            );
            if(allOK){
                if(changed){
                    const save_parsed = parsed.map(o => o);
                    this._data = parsedWithInterp.map(({y}) => y);
                    super.parse(start, count);
                    this._cachedMeta._parsed.forEach((o, i) => {o.y = save_parsed[i].y});
                }
            }
            else{
                console.warn('x data missing, could not interpolate'+
                    (((meta.index || meta.index === 0) && ' dataset '+meta.index) ?? ''))
            }
        }
    }
}

Chart.register(LineControllerWithGapInterpolation);

var config_working = {
    type: 'line',

    data: {
        labels: ['01 May 2023', '02 May 2023', '03 May 2023', '04 May 2023', '05 May 2023', '06 May 2023', '07 May 2023', '08 May 2023', '09 May 2023', '10 May 2023', '11 May 2023'],

        datasets: [{
            label: 'Full History',
            borderColor: '#3182BD',
            backgroundColor: '#3182BD',
            data: [20, 20.5, 21, 22, 25, 26, 24, 28, 30, 25, 22],
            // Count is 11
            fill: true
        // },{
        //     label: 'Another Full History',
        //     borderColor: '#BD8231',
        //     backgroundColor: '#BD8231',
        //     data: [20, 12, 21, 22, 25, 25.5, 24, 28, 30, 26, 22],
        //     // Count is 11
        //     fill: true
        },{
            label: 'Gappy data',
            borderColor: '#2c6145',
            backgroundColor: '#2c6145',
            data: [15, 14, 12, undefined, undefined, undefined, undefined, 12, 10, 14, 15],
            //should be: [35, 29, 33,----,40,40,39,37]
            // Count is 11
            fill: true,
        }]
    },

    options: {
        type: 'line',
        spanGaps: true,
        responsive: true,
        maintainAspectRatio: false,
        pointRadius: 0,
        scales: {
            y: {
                stacked: true,
                beginAtZero: true
            }
        }
    }
};

var config_problem = {
    type: 'line_with_gap_interpolation',

    data: {
        labels: ['01 May 2023', '02 May 2023', '03 May 2023', '04 May 2023', '05 May 2023', '06 May 2023', '07 May 2023', '08 May 2023', '09 May 2023', '10 May 2023', '11 May 2023'],

        datasets: [{
            label: 'With one gap',
            borderColor: '#3182BD',
            backgroundColor: '#3182BD',
            data: [20, undefined, 21, 22, 25, 26, 24, 28, 30, 25, 22],
            fill: true
        // },{
        //     label: 'With two gaps',
        //     borderColor: '#BD8231',
        //     backgroundColor: '#BD8231',
        //     data: [20, 12, 21, 22, 25, undefined, 24, 28, 30, undefined, 22],
        //     fill: true
        },{
            label: 'Gappy data',
            borderColor: '#2c6145',
            backgroundColor: '#2c6145',
            data: [15, 14, 12, undefined, undefined, undefined, undefined, 12, 10, 14, 15],
            fill: true
        }]
    },

    options: {
        type: 'line',
        spanGaps: true,
        responsive: true,
        maintainAspectRatio: false,
        pointRadius: 0,
        scales: {
            y: {
                stacked: true,
                beginAtZero: true
            }
        }
    }
};

window.onload = function() {
     var ctx = document.getElementById('cvs-working').getContext('2d');
     window.myLine = new Chart(ctx, config_working);

    var ctx = document.getElementById('cvs-problem').getContext('2d');
    window.myLine = new Chart(ctx, config_problem);
};
<div style="width:46vw; height: 300px; display: inline-block">
    <canvas id="cvs-working"></canvas>
</div>
<div style="width:46vw; height: 300px; display: inline-block">
    <canvas id="cvs-problem"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.3.0/chart.umd.js" integrity="sha512-CMF3tQtjOoOJoOKlsS7/2loJlkyctwzSoDK/S40iAB+MqWSaf50uObGQSk5Ny/gfRhRCjNLvoxuCvdnERU4WGg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

相关问题