D3.js: D3 with Backbone.js-Marionette.js

Marionette.js is a reliable sidekick of Backbone.js, and an almost de-facto plugin of the framework. It reduces repetitive code so that you don’t have to invent wheels.

In D3, data is very close to the view. It binds data and view element. But Bakcbone.js aims to separate data model and view.
So I brokedown D3’s data binding, as well as event listener. The framework is responsible for such tasks. Now D3 is responsible for rendering and animation of svg.

This snippet has multiple item view for D3 with the event lister, realtime data updating, and dynamic attribute change along with data.
This is a template of the future usage. So only one view can react the change.

The link of the page is here.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">

  <style>
  form#form { width: 20%;}
  </style>
</head>
<body>

  <h1>D3.js with Backbone-Marionette</h1>

  <div class="users">
  </div>
  
  <!-- Canvas of D3 view.  -->
  <svg id="svg" width="500" height="500"></svg>
  
    <!-- template -->
    <script id="taskTemplate" type="text/template">
      <form id="form" action="">
        <label for="text"><%= title%></label>
        <input type="range" class="range" name="range" min="0" max="200"><!-- range input -->
        <input type="submit" class="submit" value="Change Number">
      </form>
    </script>

  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
  <script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js"></script>
  <script src="../../lib/backbone/backbone.marionette.min.js"></script>
  <script src="http://d3js.org/d3.v3.min.js"></script>

  <script>
(function(){
  window.App = {
    App: {},
    Models: {},
    Collections: {},
    Views: {},
    D3: {}
  };


  // ------------------- class definition ------------------
  // App.
  App.App = Marionette.Application.extend({
    initialize: function(data) {
  
      // Instantiate the collection with data.
      App.titleCollection = new Backbone.Collection(data);
  
      var titleListView = new App.Views.Users({
        collection: App.titleCollection,
        el: '.users'
      }).render();

    },
  });
  
  // Item View
  App.Views.User = Backbone.Marionette.ItemView.extend({
    initialize: function(){
      // Render D3 view.
      this.d3Render();
    },
    
    template: '#taskTemplate', // refer template's ID.
    tagName: 'div',
    collection: '',
    model: '',
    el: '',
    ui: {
      submitButton: '.submit',
    },
    
    events: {
      'click @ui.submitButton': function(e){
        e.preventDefault();
        
        // Get the model data.
        var point = this.model.get("point");
        
        // Get input data by "$(this.el).find(".text").val()".
        point[0] = parseInt($(this.el).find(".range").val());
        this.model.set({ point:point });
        
        // After updating model, rerender D3 view.
        this.d3Update();
      }
    },
    
    modelEvents: { 'change': function(){
      // "this.render()" is needed to reflect changing model in the view.
      this.render();
      console.log("model changed");
    }},
    // Event listener for changing collection.
    collectionEvents: { 'change': function(){
      console.log("collection added");
    }},
    
    // D3-rendering function.
    d3Render: function(){
      var width = 200,
      height = 200;
      
      // Put global namespace below App.
      App.D3.svg = d3.select("#svg")
      .append("g");
      
      // Store the method chain as "this.circle" per model view.
      this.circles = App.D3.svg.selectAll("circle").data(this.model.get("point")).enter()
      	.append("circle")
      	.attr("cx", function(d){ return d;})
      	.attr("cy", function(d,i){ return i*50 + 50;}) // Change y by index of the data.
      	.attr("r", 5)
      	.attr("fill", this.model.get("title"));
    },
    
    // D3-Updating function.
    d3Update: function(){
      
      this.circles.data(this.model.get("point"))
      .transition()
      .attr("cx", function(d){ return d;}); // Change only x by form input.
      
      console.log('I clicked the button!');
    }  
  });
  
  // Collection View
  App.Views.Users = Backbone.Marionette.CollectionView.extend({
    initialize: function(){},
    
    tagName: 'div', 
    childView: App.Views.User,
    childEvents: {
      render: function(){ console.log("childView rendered"); }
    }
  });


  // ----------------- Instantiation ----------------------
  
  // Collection data.
  var data = [
    {point: [10,20,30], title: "yellow"},
    {point: [30,40,60], title: "red"},
    {point: [5,100,40], title: "blue"}
  ];
  
  // Instantiate App.
  var myApp = new App.App(data);
  myApp.start();

})();
  </script>


</body>
</html>

Leave a Reply