David-Desmaisons / vue-plotly

📈 vue wrapper for plotly.js
https://david-desmaisons.github.io/vue-plotly/
MIT License
255 stars 74 forks source link

Plots not updating on data changes #10

Open Tehsurfer opened 4 years ago

Tehsurfer commented 4 years ago

Hi there! I think the calls to Plotly.react may be broken? (or I could be changing them incorrectly ... I just started Vue.js)

Try this in your App.vue to recreate it

<template>
  <div id="app">
    <plotly :data="plot_data" :layout="layout"></plotly>
    <dat-gui closeText="Close controls" openText="Open controls" closePosition="bottom">
      <dat-string v-model="layout.title" label="Title" />
      <dat-number v-model="plot_data[0].y[0]" :min="-100" :max="100" :step="1" label="Offset y[0]"/>
    </dat-gui>
    <h3>{{layout}}</h3>
<h3>Y values are currently: {{this.plot_data[0].y}}</h3>

  </div>
</template>

<script>
// the javascript below goes here
</script>
import {Plotly}from "vue-plotly";
import Vue from "vue";
import DatGui from "@cyrilf/vue-dat-gui";
Vue.use(DatGui);

export default {
  name: "app",
  components: { Plotly },
  data() {
    return {
      layout: {
        title: 'Plot update test'
      },
      plot_data: [{
        x: [1,2,3,4],
        y: [10, 15, 13, 17],
        type: "scatter"
      }]
    };
  },
};
ped59430 commented 4 years ago

Try plotly in lower case:

<plotly
  :data="plot_data"
  :layout="layout"
/>
Tehsurfer commented 4 years ago

captured Sorry I should have left something to explain what my issues was in more detail. Here is a gif that should explain the chart not updating

ped59430 commented 4 years ago

Oh! And do you have Vue Devtools installed? You could see what the values are in your component, in the controls component and in the Plotly component, just by opening the Development tools like this: image

If you would make a codepen or a jsfiddle, I could have a look, but I cannot see why it is not updating at first glance.

Either there is a reactivity problem related to using data-gui, either something else I do not understand for the moment.

Tehsurfer commented 4 years ago

Yeah I do! Data is is reactive in the 'Plotly' component but not reactive on the chart. I'm thinking there are some issues with the underlying plotly.react call

ped59430 commented 4 years ago

Ok ok, that's weird because I use the graphs in my app with a lot of reactivity

Tehsurfer commented 4 years ago

ahhhh ok that's good to know. Must just be me then.

Tehsurfer commented 4 years ago

Well thanks for your help @ped59430. I've actually switched to another solution for charting now anyways after a bit more experience with Vue

ped59430 commented 4 years ago

Ok, the issue was created a long time ago! Just as a side note, I've also tried the other vue plugin (the one from statnett) and experienced some problems with responsiveness. I would be interested to know about your solution, maybe directly by mail if you prefer!

Tehsurfer commented 4 years ago

So I actually found statnett's one to be quite responsive, I'll share with you how I used his one.

I'm now leaning away from the wrappers though and just calling Plotly directly in a small component. What solution do you currently use?

ped59430 commented 4 years ago

Ok for statnett! I must have found a special case. I found another one with this wrapper too anyway (I ve posted my "special" case in this issue #11) I actually use this wrapper and build my own component around it to fetch the data. So now you handle the call to plot or replot on your own, and you have a complete control on options, resposiveness? Might be a good idea for me too...

Tehsurfer commented 4 years ago

Have a look at this demo for what I used to make statnett's version reactive

The code of interest is here (albeit a bit messy 😨): https://github.com/Tehsurfer/vue-plotsvy/blob/netlify-hosting/src/App.vue

ped59430 commented 4 years ago

Thanks!

Tehsurfer commented 4 years ago

So now you handle the call to plot or replot on your own, and you have a complete control on options, resposiveness? Might be a good idea for me too...

Yeah I started switching to that yesterday, I'm hoping it should give more control.

<template>
  <div class="plot-container">
      <div id='plot'></div>
  </div>
</template>

<script>

import CsvManager from "./csv_manager"
var csv = new CsvManager()
import Plotly from 'plotly.js-dist/plotly'

export default {
  name: "PlotVuer",
  beforeCreate: function() {
  },
  methods: {
    loadURL: function(url) {
      csv.loadFile(url).then( () => {
        this.pdata[0].x = csv.getColoumnByIndex(0).shift();
        this.pdata[0].y = csv.getColoumnByIndex(1).shift();
        this.pdata[0].type = csv.getDataType()
        this.items = csv.getHeaders();
        this.plot_channel(csv.getHeaderByIndex(1))
        return true
      });
    },
    plot_channel: function(channel){
      this.layout.title = channel
      window.pdata = this.pdata
      this.pdata[0].y = csv.getColoumnByName(channel)
      window.ppplot = this.plot
      Plotly.plot('plot', this.pdata)
    },
    react() {
      return Plotly.react('plot', this.pdata)
    }
  },
  props: { url: String},
  data: function() {
    return {
      items: ['first', 'second', 'third'],
      pdata: [{ x: ['1', '2', '3', '4'], y: [10, 25, 20, 50], type: 'scatter' }],
      layout: {
        title: "edit this title"
      },
      channel: 'Select a channel',
      plot: undefined
    };
  },
  computed: {
  },
  created(){
    this.loadURL(this.url)
  },
  mounted(){
    this.$watch('data', () => {
      this.react()
    }, { deep: !this.watchShallow })
    this.$watch('url', () => {
      this.loadURL(this.url)
    }, { deep: !this.watchShallow })
  }
};
</script>
ped59430 commented 4 years ago

Yeah! And it redraws on data changes?

Tehsurfer commented 4 years ago

ahhhhh nope, I need to add that today actually 😅

What's your email? I'd like to DM you a question.

Tehsurfer commented 4 years ago

Edit I just edited the above snippet so that it now redraws on changes. I copied statnet's idea for it and used this.$watch.

David-Desmaisons commented 4 years ago

@Tehsurfer , did you try to remove this watch?

this.$watch('data', () => { this.react() }, { deep: !this.watchShallow })

Tehsurfer commented 4 years ago

@Tehsurfer , did you try to remove this watch?

Hmmmm, no I didn't modify the library if that's what you mean?

Also, having a look, you use this.$nextTick don't you?

https://github.com/David-Desmaisons/vue-plotly/blob/0360a0cdf6e8b496ad3ee089c119ef429c2a8682/src/components/Plotly.vue#L90

David-Desmaisons commented 4 years ago

I mean the watch in the code sample you share here as you wouldn't need to watch data by yourself.

David-Desmaisons commented 4 years ago

Also, having a look, you use this.$nextTick don't you?

yes.

Tehsurfer commented 4 years ago

I mean the watch in the code sample you share here as you wouldn't need to watch data by yourself.

Oh yeah, that example updates successfully, check it out here: https://vue-plotsvy-demo.netlify.com/

rkube commented 4 years ago

I'm also having a problem with that my plot is not updating when the data is changing.

Here is my App.Vue

<template>
  <div id="app">
    <PlotlyTest></PlotlyTest>
  </div>
</template>

<script>
import PlotlyTest from "./components/PlotlyTest.vue";
export default {
  components: {
    PlotlyTest
  },
  computed: {
    code() {
      const fromAttr = Object.keys(this.data.attr)
        .map(key => `:${key}="${this.data.attr[key]}"`)
        .join(" ");
      return `<plotly :data="data" :layout="layout" ${fromAttr}/>`;
    }
  }
};
</script>

and here is my test component:

<template>
  <div>
    <plotly :data="data" :layout="layout" />
    <button v-on:click="button_click_func">You clicked me {{ count }} times.</button>
  </div>
</template>

<script>
import { Plotly } from "vue-plotly";

export default {
  components: {
    Plotly
  },
  data: function() {
    return {
      count: 0,
      message: 'not updated',
      data:[{ x: [1,2,3,4], y: [10,15,13,17], type:"scatter" }],
      attr: { displayModeBar: true },
      layout: { title: "My graph :)" }
    };
  },
  methods: {
    button_click_func: function() {
      console.log("Clicked the button: " + this.count, " y[0] = ", this.data[0].y[0]);
      this.count += 1;
      // See https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
      this.$set(this.data[0].y, 0, this.data[0].y[0] + 1)
    },
    react: function() {
      console.log("I am reacting")
    }
  },
  mounted() {
    this.$watch("count", () => {
      console.log("Watching data!");
      this.react()},  { deep: !this.watchShallow })
  }
};
</script>

I was trying to call Plotly.react directly in the react function. I tried this here

return Plotly.react('plot', this.data)

But this only gave me an error [Vue warn]: Error in callback for watcher "count": "TypeError: vue_plotly__WEBPACK_IMPORTED_MODULE_0__.Plotly.react is not a function"

rkube commented 4 years ago

@Tehsurfer I'm trying your approach of using Plotly directly within Vue components. Using your code from above I get the error "Error: "No DOM element with id 'plot' exists on the page.". Do you know why it can't find the element from the template there?

Tehsurfer commented 4 years ago

Yes I think I have an idea why. I wasn't using global DOM names instead of passing in a vue reference to the DOM (We need to do the latter).

I now call react like so:

<template>
  <div ref="plotContainer" ></div>
</template>
react() {
  return Plotly.react(this.$refs.plotContainer, this.data, this.layout, this.getOptions())
 },
rkube commented 4 years ago

@Tehsurfer Oh, I see. I didn't know how this works. So vue can't resolve references by value (equivalent to passing the string "plotContainer" in the Plotly.react call). Instead it creates a reference to the DOM component, called this.$refs.plotContainer". This is also explained in the vue docs

I solved it by passing the ref tag for the plotly plot as a prop. My vue code is inside a component, that way I can instantiate multiple components on the same page and each component has a differently named plot div.

The example below uses websockets. In the created() function I register a socket callback on the event "new_data". This callback updates the plotly plot, as references by the prop "plotid".

<template>
  ...
  <div class="column" style="background-color:#bbb;">
    <div :id="plotid"></div>
  </div>
</template>

And the component code updates the plot like this


export default {
...
  props: ["plotid"],
  created() {
      // I need to reference this in a local variable for access in the socket.on call
      var vm = this;
      socket.on("new_data", function(msg) {

        Plotly.restyle(vm.$props.plotid, update);
     }
  }
}
Tamara-33 commented 3 years ago

Hello!! I am with a similar problem, although it is a 3D graph made with plotly.js and has a dat.gui controller that modifies one of the parameters. My problem is that when changing the parameter, it makes me a new graph over the previous one, it does not refresh the graph. I hope you can help me.


<head>
    <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
    <script src="dat.gui.js"></script>
</head>

<body>
    <div id='myDiv'></div>
<script >

var button_layer_1_height = 1.2
var button_layer_2_height = 1.1
var annotation_offset = 0.04

    //Defino parámetro a controlar con dat.gui      

var zPts = []; 
var xPts = [];
var yPts = [];
  //Control GUI

  var updateGraphFunc = function(){

var G=0.1;

for(x=-3; x<=3; x+= 0.01) {
  let zdat = [];
  let ydat = [];
  let xdat = [];
  for (y=0; y<=0.5; y+=0.01) {
    zdat.push( 2*0.0625*[Math.sqrt(2/ (Math.PI* (params.G**2 + (y**2 / params.G**2)))) * Math.exp(-2*((x**2)+0.25) / [params.G**2 + (y**2/params.G**2)])] * [Math.cosh(2*x/(params.G**2 + (y**2/params.G**2))) + Math.cos( (2*x*y) / params.G**4 + y**2)] );
    ydat.push(y);
    xdat.push(x);
  }
  zPts.push(zdat);
  yPts.push(ydat);
  xPts.push(xdat);
  }

var data = [{
    z: zPts,
    x: xPts,
    y: yPts,
    type: 'surface',
    colorscale:'Jet',
    contours: {
    z: {
      show:true,
      usecolormap: true,
      highlightcolor:"#42f462",
      project:{z: true}
    }
  }

  }];  
  Plotly.newPlot('myDiv', data, layout);
  }

  var updateGraph = function() { updateGraphFunc(); }

  var updatemenus=[

    {
        buttons: [
            {
                args: ['colorscale', 'Viridis'],
                label: 'Viridis',
                method: 'restyle'
            },
            {
                args: ['colorscale', 'Electric'],
                label:'Electric',
                method:'restyle'
            },
            {
                args: ['colorscale', 'Earth'],
                label:'Earth',
                method:'restyle'
            },
            {
                args: ['colorscale', 'Hot'],
                label:'Hot',
                method:'restyle'
            },
            {
                args: ['colorscale', 'Portland'],
                label:'Portland',
                method:'restyle'
            },
            {
                args: ['colorscale', 'Blackbody'],
                label:'Blackbody',
                method:'restyle'
            },
        ],
        direction: 'left',
        pad: {'r': 10, 't': 10},
        showactive: true,
        type: 'buttons',
        x: 0.15,
        xanchor: 'left',
        y: button_layer_1_height,
        yanchor: 'top'
    },
]
var annotations = [

    {
      text: 'Colorscale:',
      x: 0,
      y: button_layer_1_height - annotation_offset,
      yref: 'paper',
      align: 'left',
      showarrow: false
    },
]

  var layout = {
    autosize: false, 
    width: 1300,
    height: 600,
    margin: {t: 130, b: 0, l: 0, r: 0},
    updatemenus: updatemenus,
    annotations: annotations,
    scene: {
        xaxis:{
            gridcolor: 'rgb(255, 255, 255)',
            zerolinecolor: 'rgb(255, 255, 255)',
            showbackground: true,
            backgroundcolor:'rgb(230, 230,230)'
        },
        yaxis: {
            gridcolor: 'rgb(255, 255, 255)',
            zerolinecolor: 'rgb(255, 255, 255)',
            showbackground: true,
            backgroundcolor: 'rgb(230, 230, 230)'
        },
        zaxis: {
            gridcolor: 'rgb(255, 255, 255)',
            zerolinecolor: 'rgb(255, 255, 255)',
            showbackground: true,
            backgroundcolor: 'rgb(230, 230,230)'
        },
        aspectratio: {x: 1, y: 1, z: 0.7},
        aspectmode: 'manual'
  }
  };

         gui = new dat.GUI();
          params ={G:0.1};

         var folder0 = gui.addFolder('Parameters');
           var GGUI= folder0.add(params, 'G');
            folder0.open();

          //folder0.G++;
          //GGUI.updateDisplay()
            GGUI.onChange( updateGraphFunc );

    updateGraphFunc();

</script>
</body>
x12a1f commented 2 years ago

I think I have the same issue.

Whenever I add values to the data[0].x and data[0].y arrays, the chart does not update.

When I add a value to the x and y arrays, I log them in the console. When such a message is added to the console, the x and y arrays as shown in the vue devtool both grow by a single entry. However, line in the chart never updates and stays the same.

2022-02-19_14-14 2022-02-19_14-15

The relevant parts of the code:

<template>
  <div>
    <plotly :data="data" :layout="layout" :displaylogo="false"></plotly>
  </div>
</template>

<script lang="ts">
...
@Component({
  components: { Plotly },
})
export default class GraphView extends Vue {
  data = [];
  layout = {};
...
  @Watch('topicValue')
  valueChanged(value: TopicValue) {
    console.log("add : %s = %s", value.timestamp.format(), value.value);
    this.data[0].x.push(value.timestamp);
    this.data[0].y.push(value.value);
  }
...
  mounted() {
    axios
      .get(.....)
      .then((response) => {
        let data = {
          x: [],
          y: [],
          type: "scatter",
          line: {
            width: 1,
          },
        };
        response.data.values.forEach((record) => {
          data.x.push(record.timestamp);
          data.y.push(record.value);
        });

        this.data.push(data);
      });
masimo12358 commented 1 year ago

I have the same exact issue. When data added to the X and Y arrays, the plot is not updated.