Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chart with live data source + decimation + zoom plugins not drawing new points when samples exceed decimation threshold #11929

Open
twilson90 opened this issue Oct 14, 2024 · 2 comments

Comments

@twilson90
Copy link

twilson90 commented Oct 14, 2024

Expected behavior

I have a line chart that updates every second with live data.
Every time data is received it is pushed onto a dataset.data. (not re-constructed)
I have enabled decimation and zoom because the amount of data points can get into tens of thousands.
All works well until I zoom out enough that the decimation threshold is passed, at which point the graph stops adding points to the canvas.
The scale continues to grow however which suggests a bug.
If I disable decimation then the problem doesn't occur.

Presumably I have to do something to refresh the internal _data buffer but there's nothing in the docs I can find that suggests anything.

Current behavior

What it looks like after zooming out and waiting:
chrome_CPt5mrz4pC

Closer to what it should look like:
image_3

Optional extra steps/info to reproduce

this.chart = new Chart(this.canvas, {
  type: "line",
  data: {},
  options: {
      normalized: true,
      parsing: false,
      spanGaps: true,
      onHover: (e)=>{
          this.canvas.style.cursor = "crosshair"
      },
      animation: false,
      maintainAspectRatio: false,
      responsive:true,
      scales: {
          x: {
              display: 'auto',
              type: "linear",
              min: 0,
              max: 60*1000,
              ticks: {
                  autoSkip: true,
                  maxRotation: 0,
                  callback: (value, index, values)=>{
                      if (index == 0 || index == values.length - 1) return null;
                      return ms_to_timespan(value);
                  }
              },
          },
          y: {
              display: 'auto',
              type: "linear",
              ticks: {
                  callback: (value, index, values)=>{
                      return this.#format_value(value)
                  }
              },
          }
      },
      plugins: {
          zoom: {

              limits: {
                  x: {
                      minRange: 10*1000
                  },
              },
              pan: {
                  enabled: true,
                  mode: 'x',
                  onPanStart:(c, ...args)=>{
                      this.panning = true;
                      this.update();
                  },
                  onPanComplete:(c)=>{
                      this.panning = false;
                      this.update();
                  }
              },
              zoom: {
                  wheel: {
                      enabled: false,
                  },
                  pinch: {
                      enabled: false
                  },
                  mode: 'x',
                  onZoomStart:()=>{
                      this.zooming = true;
                      this.update();
                  },
                  onZoomComplete:(c)=>{
                      this.zooming = false;
                      this.update();
                  }
              }
          },
          tooltip: {
              callbacks: {
                  title: (ctxs)=>{
                      return ctxs.map(ctx=>ms_to_timespan(ctx.raw.x)).join(", ");
                  },
                  label: (ctx)=>{
                      return `${this.#parse_key(ctx.dataset.label)[0]}: ${this.#format_value(ctx.raw.y)}`;
                  }
              }
          },
          legend: {
              labels: {
                  boxWidth: Chart.defaults.font.size,
                  generateLabels: (c)=>{
                      var items = Chart.defaults.plugins.legend.labels.generateLabels(c);
                      for (var i of items) {
                          i.text = this.#parse_key(i.text)[0];
                      }
                      return items;
                  }
              },
              onHover: ()=>{
                  this.canvas.style.cursor = "pointer";
              },
              onLeave: ()=>{
                  this.canvas.style.cursor = "";
              },
              onClick: (e, legendItem, legend)=>{
                  Chart.defaults.plugins.legend.onClick(e, legendItem, legend);
                  this.update();
              }
          },
          /* decimation: {
              enabled: true,
              algorithm: 'min-max',
              samples: 100,
              threshold: 100
          }, */
      }
  }
});

Whenever I want to add data:

this.chart.data.datasets = Object.entries(raw_data).map(([key,{data,length}], i)=>{
  let dataset = this.chart.data.datasets.find(d=>d.label==key);
  dataset = dataset ?? {
      label: key,
      borderWidth: 1.0,
      pointRadius: 1.5,
      pointHitRadius: 2,
      pointStyle: "rect",
      fill: false,
      tension: 0.5,
      borderJoinStyle: "round",
      data: []
  };
  dataset.borderColor = graph_colors[i%graph_colors.length];
  if (data && length) {
      for (var i = dataset.data.length; i<length; i++) {
          let [x,y] = data[i];
          dataset.data.push({x,y});
      }
  }
  return dataset;
});

Possible solution

Some way of refreshing decimate's _data buffer referenced in the docs?

Has to be efficient though, can't regenerate from scratch with each new sample.

Context

No response

chart.js version

4.4.4

Browser name and version

No response

Link to your project

No response

@twilson90
Copy link
Author

twilson90 commented Oct 15, 2024

Figured out a solution, when appending samples with decimation enabled I now do:

dataset.data.push(...samples);
if (dataset._data) dataset._data.push(...samples);

And if I'm retrieving a sample from a dataset, I refer to _data if it exists, otherwise data.

@etimberg
Copy link
Member

Thanks for reporting this @twilson90. It definitely sounds like a bug in the decimation plugin since you shouldn't need to care about _data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants