2

I'm trying to plot an image/heatmap with a slider that will change the opacity (of the heatmap), and a second slider that will modify a custom parameter on each "onchange" event.

Once this image/heatmap is rendered, no computation should be done, and moving the sliders should be instantaneous. But from what I have tried, moving one slider is very slow (1 second lag between each position), and uses max CPU %.

I'm looking for a JS-only solution (no Python for this part).

How to make a faster slider rendering with Plotly JS?

enter image description here

var z = [], steps = [], i;
for (i = 0; i < 500; i++) 
    z.push(Array.from({length: 600}, () => Math.floor(Math.random() * 100)));  
for (i = 0; i < 100; i++)
    steps.push({ label: i, method: 'restyle', args: ['line.color', 'red']});
var data = [{z: z, colorscale: 'YlGnBu', type: 'heatmap'}];
var layout = {title: '', sliders: [{
    pad: {t: 5},
    len: 1,
    x: 0,
    currentvalue: {
      xanchor: 'right',
      prefix: 'i: ',
      font: {
        color: '#888',
        size: 20
      }
    },
    steps: steps
  }]};
Plotly.newPlot('myDiv', data, layout);
<script src="https://cdn.plot.ly/plotly-2.16.2.min.js"></script>
<div id="myDiv"></div>
EricLavault
  • 12,130
  • 3
  • 23
  • 45
Basj
  • 41,386
  • 99
  • 383
  • 673
  • Idea: can we move the slider to a separate div, with no plot/graph ? we would use two `Plotly.newPlot`, one for the heatmap, and the other without any data, but just the slider. Is this possible? I couldn't make the latter. – Basj Nov 22 '22 at 16:26
  • This is because you are using `method: 'restyle'` with the wrong `args` (there is no line, but it seems to be as slow as if there were many lines, I suspect it defaults to restyling the z data points instead), which is slow. But what are you trying to do ? Is it related to the opacity question ? – EricLavault Nov 24 '22 at 17:57
  • @EricLavault Yes one slider will change the opacity (of this heatmap) and a second slider will modify a parameter that will be notified to server (with a AJAX / `fetch`) on each "onchange" event. I'm looking for a JS-only solution (no Python for this part). I'm opening a bounty because I'm curious about the standard plotly.js way to implement this. – Basj Nov 25 '22 at 19:42

1 Answers1

4

This is because you are using method: 'restyle' with the wrong args, ie. ['line.color', 'red'] the syntax is not correct and there is no line so I guess Plotly (without knowing what to restyle exactly) just redraws the whole plot whenever the slider moves, which is slow.

Basically, you can use the same slider configuration in javascript and in python for the same task (in the end the same Plotly.js slider component will be used).

For example, one can set the opacity of an image according to the slider's position, but for the changes to be applied instantly one needs to set the proper method and args in the slider' steps configuration, excactly as explained in this post :

steps.push({
  label: i,
  execute: true,
  method: 'restyle',
  args: [{opacity: i/100}]
});

Here is a full example with two sliders : one that changes the opacity of the heatmap and another one that doesn't touch the plot but only triggers a specific handler :

const z = [];
for (let i=0; i<500; i++) {
  z.push(Array.from({length: 600}, () => Math.floor(Math.random() * 100)));
}

const data = [{z: z, colorscale: 'YlGnBu', type: 'heatmap'}];

// Steps for the heatmap opacity slider
const opacity_steps = [];
for (let i = 0; i <= 100; i++) {
  opacity_steps.push({
    label: i + '%',
    execute: true,
    method: 'restyle',
    args: [{opacity: i/100}]
  });
}

// Steps for the custom slider
const custom_steps = [];
for (let i = 50; i <= 200; i++) {
  custom_steps.push({
    label: i,
    execute: false,
    method: 'skip',
  });
}

const layout = {
  title: '',
  sliders: [{
    name: 'opacity_slider',
    steps: opacity_steps,
    active: 100,
    pad: {t: 30},
    currentvalue: {prefix: 'opacity: '}
  }, {
    name: 'custom_slider',
    steps: custom_steps,
    pad: {t: 120},
    currentvalue: {prefix: 'i: '}
  }]
};

Plotly.newPlot('graph', data, layout);

// Retrieve the graph div
const gd = document.getElementById('graph');

// Attach 'plotly_sliderchange' event listener to it (note that we can't specify
// which slider the handler is taking care of using a secondary selector)
gd.on('plotly_sliderchange', function(event) {
  // ... so we have to distinguish between the two sliders here.
  if (event.slider.name != 'custom_slider')
    return;

  const slider = event.slider;       // the slider emitting the event (object)
  const step = event.step;           // the currently active step (object)
  const prev = event.previousActive; // index of the previously active step

  const value = step.value;          // captain obvious was here
  const index = step._index;         // index of the current step

  // ...
});
<script src="https://cdn.plot.ly/plotly-2.16.2.min.js"></script>
<div id="graph"></div>
EricLavault
  • 12,130
  • 3
  • 23
  • 45
  • last little thing: would you know how to remove the ticks and text under the slider? – Basj Nov 28 '22 at 09:59
  • 1
    One can remove ticks by setting `tickwidth: 0` in the slider config. The text under the slider corresponds to the labels of each (visible) steps, which we can leave empty `label: ''`, but it will also remove the label of the `currentValue` for which we can only set the prefix/suffix (it always takes the label of the active step). That said, it should be possible to draw it manually within the 'plotly_sliderchange' handler. I upvoted your question, I'm also gonna edit it just a bit to add the details we discussed in the comments, maybe downvoters will change their mind ;) – EricLavault Nov 28 '22 at 19:14