1

I am making a little productivity app/game to make my self feel better about all the time I waste. I am using vue and chartjs and the don't seem to play nice with each other.

When I include my <canvas> element in <div id="app">, the one vue is initiated on chartjs fails to render anything. There are no errors or anything. However, when I move <canvas> outside of <div id="app"> it works perfectly. Doesn't anyone have any insight in this?

HTML

<body>

  <div id="app">
    <div id="header">
      <h1>GAME OF LIFE!</h1>
    </div>
    <div class="character_container">
      <div class="health_bar">
        <div class="life"></div>
      </div>
      <div class="character">
        <img id="sprite" src="imgs/melee/1.png" width="100" height="100px">
        <div class="stats">
          <p>ed</p>
          <p>3</p>
          <p>3</p>
          <p>da</p>
          <p>adf</p>
          <p>adf</p>
        </div>
      </div>
    </div>
    <div id="dashboard">
      <h2>GAME DATA</h2>
      <input type="date" name="viewDate" v-model="viewDate">
    </div>
    <!-- Doesn't work when canvas inside #app -->
    <div class="canvas">
      <canvas></canvas>
    </div>
  </div>
  <!-- Works when the canvas is outside #app -->
  <!-- <div class="canvas">
    <canvas></canvas>
  </div> -->

  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"></script>
  <script src="vue.js"></script>
  <script src="app.js"></script>
</body>

Javascript

    const day = ( x => {
      let date = (t => new Date(t.getFullYear(), t.getMonth(), t.getDate(), 0, 0, 0))(new Date());
      return new Date(date.setDate(date.getDate() + x));
    });
    const padDate = (x => (x.toString().length <= 1? '0'+ x : x));
    const format = (x => formatDate(new Date(x)));
    const formatDate =  (fd => padDate(fd.getUTCMonth() + 1)+'/'+padDate(fd.getUTCDate())+'/'+fd.getUTCFullYear()+' 00:00');

    const serverUrl = 'http://localhost:3000/logs';
    const ctx = document.querySelector("canvas").getContext("2d");

    const chartConfig = {
      type: 'bar',
      data: {
        labels: [],
        datasets: [{
          type: 'bar',
          label: 'Productivity',
          backgroundColor: 'rgba(255, 0, 0, .5)',
          borderColor: 'rgba(255, 0, 0, .5)',
          data: [],
        }, {
          type: 'line',
          label: 'Code Written',
          backgroundColor: 'rgba(0, 255, 0, .5)',
          borderColor: 'rgba(0, 255, 0, .5)',
          fill: false,
          data: [],
        }, ]
      },
      options: {
                title: {
                    text:"Productivity Graph"
                },
        scales: {
          xAxes: [{
            type: "time",
            display: true,
            time: {
              format: 'MM/DD/YYYY HH:mm',
              round: 'day',
              unit: 'day'
            }
          }],
        },
      }
    };

    function fetchData() {
      return fetch(serverUrl).then(function (res){
        return res.json();
      })
    }

    function dateToISO(date) {
      var msec = Date.parse(date);
      return new Date(msec).toISOString().substring(0, 10);
    }



    function updateLog(logData){
      const headers = {
        headers: {
          'Access-Control-Allow-Origin':'*',
          'Content-Type': 'application/json'
        },
        method: "PUT",
        body:  JSON.stringify(logData)
      };
      return fetch('http://localhost:3000/logs',headers).then(function(res){
        return res.json();
      });
    }

    function runSprite(){
      // NOTE https://www.gameart2d.com/the-robot---free-sprites.html
      // NOTE Melee(Good) 8, Run(Normal) 8, DEAD(Bad) 10
      let path = 'imgs/';
      let i = app.sprite.index;
      switch (app.sprite.status) {
        case 'good':
          path += 'melee/';
          i = (i >= 26 ? 1 : ++i);
          break;
        case 'avg':
          path += 'run/';
          i = (i >= 8 ? 1 : ++i);
          break;
        case 'bad':
          path += 'dead/';
          i = (i >= 10 ? 1 : ++i);
          break;  
      }
      path += i;
      app.sprite.index = i;
      document.querySelector('#sprite').src = path + ".png";
    }

    setInterval(runSprite, 100);

    var app = new Vue({
      el: '#app',
      data: {
        viewDate:  new Date().toISOString().substring(0, 10),
        sprite: {
          status: 'good',
          index: 1
        },
        logs: [],
        log:{},
        chart: chartConfig,
        line: '',
        productivityMinutesGoal: 560,
        projectsMinutesGoal: 300,
        health_bar: 100,
        visible: true
      },
      methods : {
        updateChart: function(data, build = false){
          this.logs = data;
          const productivityAverages = this.logs
                                      .map(x => x.productivity)
                                      .reduce((a, b, index, self) => {
                                         const keys = Object.keys(a)
                                         let c = {} 
                                         keys.map((key) => {
                                          c[key] = a[key] + b[key]
                                          if (index + 1 === self.length) {
                                            c[key] = c[key] / self.length
                                          }
                                         })
                                         return c
                                      });

          const projectsAverages = this.logs
                                      .map(x => x.projects.data[0].grand_total)
                                      .reduce((a, b, index, self) => {
                                         const keys = Object.keys(a)
                                         let c = {} 
                                         keys.map((key) => {
                                          c[key] = a[key] + b[key]
                                          if (index + 1 === self.length) {
                                            c[key] = c[key] / self.length
                                          }
                                         })
                                         return c
                                      });

          // above 90% equals + to health_bar;
          // below 90% equals - to health_bar; 
          // below 50% should equal death;
          this.health_bar += ((projectsAverages.hours * 60 + projectsAverages.minutes) / this.projectsMinutesGoal ) - 90;
          this.health_bar += ((productivityAverages.software_development_hours * 60) / this.productivityMinutesGoal ) - 90;
          this.health_bar = Math.round(this.health_bar);
          document.querySelector('.health_bar .life').setAttribute('style','width:'+ (100 + this.health_bar)+'%;');
          const loggedData = this.logs
                            .map(x => {return {
                              date:x.date, 
                              projectTimeMinutes:((x.projects.data[0].grand_total.hours * 60) + x.projects.data[0].grand_total.minutes - (projectsAverages.hours * 60 + projectsAverages.minutes)), 
                              productivitySoftwareMinutes: (x.productivity.software_development_hours * 60 - (productivityAverages.software_development_hours * 60))}
                            })
                            .sort((a,b) => new Date(b.date) - new Date(a.date));

          loggedData.forEach(x => {
            this.chart.data.labels.push(format(x.date));
            this.chart.data.datasets[0].data.push(x.productivitySoftwareMinutes);
            this.chart.data.datasets[1].data.push(x.projectTimeMinutes);

          });
          // Handle Build default update
          if(build){
            this.line = new Chart(ctx, this.chart);
          }else{
            this.line.update();
          }
        }  

      },
      computed: {
        dateIsValid: function (){
          return this.friend.name.length == 0 || this.friend.feature.length == 0;
        }
      },
      created: function (){
        fetchData().then(function (data){
          app.updateChart(data, true);
        });
      }
    });
4
  • You should use github.com/apertureless/vue-chartjs, it was made exactly for that. Commented Mar 1, 2018 at 4:04
  • Vue owns that canvas element - and you haven't asked it for permission to mess with it. If you place the canvas into a component (it can be simple) it will work like a charm - because you asked nicely :-) Commented Mar 1, 2018 at 4:09
  • @RandyCasburn that sounds great and all but how do I ask it nicely? Commented Mar 1, 2018 at 4:17
  • Don't say I never gave you anything :-). Please see my answer. Commented Mar 1, 2018 at 4:25

1 Answer 1

3

Here is a StackBlitz for your reference. All this does is allow Vue to have some awareness of what you are doing. Please let me know you have any other questions.

https://vue-wpbxue.stackblitz.io

<template>
 <canvas id="canvas" width="800px" height="800px"></canvas>
</template>

<script>

export default {
  name: 'App',
  methods: {
    draw: function(ctx) {
      var myChart = new Chart(ctx, {
        type: "bar",
        data: {
          labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
          datasets: [
            {
              label: "# of Votes",
              data: [12, 19, 3, 5, 2, 3],
              backgroundColor: [
                "rgba(255, 99, 132, 0.2)",
                "rgba(54, 162, 235, 0.2)",
                "rgba(255, 206, 86, 0.2)",
                "rgba(75, 192, 192, 0.2)",
                "rgba(153, 102, 255, 0.2)",
                "rgba(255, 159, 64, 0.2)"
              ],
              borderColor: [
                "rgba(255,99,132,1)",
                "rgba(54, 162, 235, 1)",
                "rgba(255, 206, 86, 1)",
                "rgba(75, 192, 192, 1)",
                "rgba(153, 102, 255, 1)",
                "rgba(255, 159, 64, 1)"
              ],
              borderWidth: 1
            }
          ]
        },
        options: {
          scales: {
            yAxes: [
              {
                ticks: {
                  beginAtZero: true
                }
              }
            ]
          }
        }
      });
    }
  },
  mounted: function() {
    var c = document.getElementById("canvas");
    var ctx = c.getContext("2d");
    ctx.translate(0.5, 0.5);
    ctx.imageSmoothingEnabled = false;
    this.draw(ctx);
  }
}
  
</script>

<style>
canvas {
  background: white;
  box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
}
</style>
Sign up to request clarification or add additional context in comments.

12 Comments

I copied the code verbatim from the codepen. The html from the component is rendering, but nothing from chartjs.
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"></script> sorry this is the one I meant to copy over, but it all seems good now.
@sorry.vol - the Vue thing is a component. It offers a draw method. Within that method the chart is created and displayed. The canvas is not restricted to 800x800, that was arbitrary, but necessary for canvas to size properly.
@sorry.vol - I updated the code in the answer and changed the link to a StackBlitz that is using Vue 3. Works like a charm. Let me know if you have any questions.
@sorry.vol - it sounds as if you need to ask a new question to address "i don't quite understand why im getting an error from the export part of the code" and "how would i go about getting that up and running" - StackOverflow frowns upon extending one question/answer with an ongoing conversation that is not directly related to the OP's question. Thanks and good luck.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.