Search code examples
javascriptbackbone.jschartschart.js

Doughnut Chart not displaying data with Chart Js and Backbone js


I'm making an application and I have to display data in a chart. I also only want the last 5 transactions entered to display.

I'm using Backbone.js and Chart.js. In my prototype displaying data was no problem because I just bootstrapped the data. But now that I'm trying to pull the data from my Backbone Collection it's not working. I only get a transparent image

// Model
(function (exports) {
    var Transaction = Backbone.Model.extend({
        defaults:  {
            amount: 0,
            transactionDate: "",
            transactionType: "debit",
            category: "miscellaneous",
            description: ""
        },
        categories: [
            "salary",
            "rent",
            "miscellaneous"
        ],
        transactionTypes: [
            "credit",
            "debit"
        ],
        initialize: function() {
                this.set({transactionDate: this.attributes.transactionDate || Date.now()}, {validate: true});
        },
        validate: function(attrs, options) {
            if (attrs['transactionType'] !== undefined && 
                !_.contains(this.transactionTypes, attrs['transactionType'].toLowerCase())) {
                return 'Invalid type: ' + attrs['transactionType'];
            } else if (attrs['category'] !== undefined && 
                !_.contains(this.categories, attrs['category'].toLowerCase())) {
                return 'Invalid category: ' + attrs['category'];
            } else if (attrs['transactionDate'] !== undefined && 
                _.isNaN(parseInt(attrs['transactionDate'])) || attrs['transactionDate'] <   0) {
                return 'Invalid date: '+ attrs['transactionDate'];
            } else if (attrs['amount'] !== undefined && 
                _.isNaN(parseInt(attrs['amount'])) || attrs['amount'] < 0) {
                return 'Invalid amount: '+ attrs['amount'];
            }
            return null;
        }
    });

    // export for global use
    exports.expensus.Models.Transaction = Transaction;

}(this));

This is the collection I'm using ..

;(function (exports) {
    var Transactions = Backbone.Collection.extend({
        // stuff and thangs
        model: expensus.Models.Transaction,
        localStorage: new Backbone.LocalStorage('TransactionsCollection'),
        latestFive: function(toJSON) {
            this.sortByDate(-1); // sort latest first

            if (!toJSON) {
                return _.first(this.models, 5);
            } else {
                var models = _.first(this.models, 5),
                        idx = -1,
                        json = [],
                        model;

                while (model = models[++idx]) {
                    json.push(model.attributes);
                }

                return json;
            }
        },
        sortByDate: function(dir) {
            dir = dir || -1;
            this.comparator = function(transaction) {
                return dir * transaction.get("transactionDate");
            };
            this.sort();
        },
        sortByAmount: function(dir) {
            dir = dir || -1;
            this.comparator = function(transaction) {
                return dir * transaction.get("amount");
            };
            this.sort();
        }
    });

    exports.expensus.Collections.Transactions = Transactions;

}(this));

And this is the Chart View, I get no errors in dev tools so I'm really at a loss ...

;(function (exports){
    var ChartView = Backbone.View.extend({
        el: ".home-page",
        template: Handlebars.compile($("#chart-template").html()),
        chart: null,
        initialize: function () {
            this.listenTo(this.collection, "add", this.render);
            this.listenTo(this.collection, "change", this.render);
            this.$(".chart-view-div").html(this.template());
            this.chart = new Chart($("#expense-chart")[0].getContext("2d"));
            this.render();
        },
        render: function() {
            var self = this;
            var data = this.chartData();
            self.chart.Doughnut(data, {
                responsive: true,
            animateScale: true
            });
        },
        chartData: function() {
            var collection = this.collection.latestFive(true);
            var data = {
                vals: [],
                labels: [],
                allData: []
            };
            var getData = function(color, highlight, labels, vals, collection) {
                var object = {
                    color: color,
                    highlight: highlight,
                    chartData: [
                        {
                            value: "",
                            label: ""
                        }
                    ]
                };
                for (var i = 0; i < labels.length; i++ ) {
                    object.chartData.push(0);
                }
                for (var j = 0; j < vals.length; j++ ) {
                    object.chartData.push(0);
                }
                for (var i = 0; i < collection.length; i++ ) {
                    var item = collection[i];
                    var label = labels.indexOf(item.category);
                    var val = vals.indexOf(item.amount);
                    object.chartData[ { value: val, label: label } ]++;
                }
                return object;
            };
            function getRandomColor() {
                var letters = '0123456789ABCDEF'.split('');
                var color = '#';
                for (var i = 0; i < 6; i++ ) {
                color += letters[Math.floor(Math.random() * 16)];
            }
            return color;
            }
            for (var i = 0; i < collection.length; i++ ) {
                var object = collection[i];
                var color = getRandomColor();   
                var highlight = getRandomColor();
                data.labels.push(object.category);
                data.vals.push(object.amount);
                data.allData.push(getData(color, highlight, data.labels, data.vals, collection));
            }
            return data;
        }
    });

    exports.expensus.Views.ChartView = ChartView;
}(this));

My Add Transaction View

;(function (exports) {
    var AddTransactionView = Backbone.View.extend({
        el: "#add-transaction-page",
        events: {
            "submit .add-transaction-form": "addTransaction"
        },
        initialize: function() {
            this.form = this.$(".add-transaction-form")[0];
        },
        addTransaction: function(evt) {
            if (evt) {
                evt.preventDefault();
            }
            var m = new expensus.Models.Transaction({
                transactionDate: Date.now(),
                transactionType: this.form["trans-type"].value,
                amount: this.form["trans-amount"].value,
                description: this.form["trans-description"].value,
                category: this.form["trans-category"].value
            });

            if(m.validationError === null) {
                this.collection.add(m);
                m.save();
                $(this.el).modal("hide");
                this.form.reset();
            } else {
                alert("Model is invalid: " + m.validationError);
            }
        }
    });

    exports.expensus.Views.AddTransactionView = AddTransactionView;
}(this));

This is as far as I could get. I've done this before with a different kind of chart but can't for the life of me figure it out with the Doughnut chart.

Thanks, everyone


Solution

  • The main thing i can see is that you pass the chart data which is not in the format that chartjs expects, so it should be an array of objects which have the properties value label and color but you are passing it something different. so a quick fix for that would be to construct an array as described

    // Model
    var Transaction = Backbone.Model.extend({
      defaults: {
        amount: 0,
        transactionDate: "",
        transactionType: "debit",
        category: "miscellaneous",
        description: ""
      },
      categories: [
        "salary",
        "rent",
        "miscellaneous"
      ],
      transactionTypes: [
        "credit",
        "debit"
      ],
      initialize: function() {
        this.set({
          transactionDate: this.attributes.transactionDate || Date.now()
        }, {
          validate: true
        });
      },
      validate: function(attrs, options) {
        if (attrs['transactionType'] !== undefined && !_.contains(this.transactionTypes, attrs['transactionType'].toLowerCase())) {
          return 'Invalid type: ' + attrs['transactionType'];
        } else if (attrs['category'] !== undefined && !_.contains(this.categories, attrs['category'].toLowerCase())) {
          return 'Invalid category: ' + attrs['category'];
        } else if (attrs['transactionDate'] !== undefined && _.isNaN(parseInt(attrs['transactionDate'])) || attrs['transactionDate'] < 0) {
          return 'Invalid date: ' + attrs['transactionDate'];
        } else if (attrs['amount'] !== undefined && _.isNaN(parseInt(attrs['amount'])) || attrs['amount'] < 0) {
          return 'Invalid amount: ' + attrs['amount'];
        }
        return null;
      }
    });
    
    
    var Transactions = Backbone.Collection.extend({
      // stuff and thangs
      model: Transaction,
      latestFive: function(toJSON) {
        this.sortByDate(-1); // sort latest first
    
        if (!toJSON) {
          return _.first(this.models, 5);
        } else {
          var models = _.first(this.models, 5),
            idx = -1,
            json = [],
            model;
    
          while (model = models[++idx]) {
            json.push(model.attributes);
          }
    
          return json;
        }
      },
      sortByDate: function(dir) {
        dir = dir || -1;
        this.comparator = function(transaction) {
          return dir * transaction.get("transactionDate");
        };
        this.sort();
      },
      sortByAmount: function(dir) {
        dir = dir || -1;
        this.comparator = function(transaction) {
          return dir * transaction.get("amount");
        };
        this.sort();
      }
    });
    
    
    var ChartView = Backbone.View.extend({
      el: ".home-page",
      template: Handlebars.compile($("#chart-template").html()),
      chart: null,
      initialize: function() {
        this.listenTo(this.collection, "add", this.render);
        this.listenTo(this.collection, "change", this.render);
        this.$(".chart-view-div").html(this.template());
        this.chart = new Chart(this.$("#expense-chart")[0].getContext("2d"));
        this.render();
      },
      render: function() {
        var self = this;
        var data = this.chartData();
        this.chart.Doughnut(data, {
          responsive: true,
          animateScale: true
        });
      },
      chartData: function() {
        var collection = this.collection.latestFive(true);
        var data = [];;
        var getData = function(color, highlight, labels, vals, collection) {
          var object = {
            color: color,
            highlight: highlight,
            chartData: [{
              value: "",
              label: ""
            }]
          };
          for (var i = 0; i < labels.length; i++) {
            object.chartData.push(0);
          }
          for (var j = 0; j < vals.length; j++) {
            object.chartData.push(0);
          }
          for (var i = 0; i < collection.length; i++) {
            var item = collection[i];
            var label = labels.indexOf(item.category);
            var val = vals.indexOf(item.amount);
            object.chartData[{
              value: val,
              label: label
            }] ++;
          }
          return object;
        };
    
        function getRandomColor() {
          var letters = '0123456789ABCDEF'.split('');
          var color = '#';
          for (var i = 0; i < 6; i++) {
            color += letters[Math.floor(Math.random() * 16)];
          }
          return color;
        }
        for (var i = 0; i < collection.length; i++) {
          var object = collection[i];
          var color = getRandomColor();
          var highlight = getRandomColor();
          data.push({
            value: object.amount,
            color: color,
            label: object.category
          });
    
        }
        return data;
      }
    });
    
    var collection = new Transactions([{
      amount: 12,
      transactionDate: 1417442176000,
      transactionType: "debit",
      category: "miscellaneous",
      description: ""
    }, {
      amount: 13,
      transactionDate: 1417442176000,
      transactionType: "credit",
      category: "salary",
      description: ""
    }]);
    var view = new ChartView({
      collection: collection
    });
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone.js"></script>
    
    <script src="http://www.chartjs.org/assets/Chart.min.js"></script>
    
    
    <script id="chart-template" type="text/x-handlebars-template">
      <canvas id="expense-chart"></canvas>
    </script>
    
    <div class="home-page">
    
      <div class="chart-view-div"></div>
    
    
    </div>