Skip to content
Irene Ros edited this page Feb 18, 2014 · 3 revisions

Chart: Composing Multiple Charts

When creating a new d3.chart based chart constructor, you may want to use charts that already exist by composing them together. For example, if one has:

A BarChart chart constructor and A PieChart chart constructor

And you wish to combine them so that each bar in your bar chart has a pie chart at the top of it, you can do so by creating one parent chart that instantiates these appror

======= STOPPPED HERE =======

Using the mixin api allows you to mix charts together to create new charts. Imagine that you have two charts:

  1. one that paints rectangles of a certain height along an x axis
  2. one that paints text labels along an x axis

You could combine those two to produce a single chart that renders rectangles with labels on them.

mixins

Imagine we have the following charts:

A labels chart that renders labels along the x axis:

labelsmixin

d3.chart("LabelChart", {
  initialize: function() {
    // setup some size defaults
    this._width = this._width   || this.base.attr("width") || 200;
    this._height = this._height || this.base.attr("height") || 200;

    // initialize a scale along which we will render 
    // the labels
    this.xScale = d3.scale.linear()
       .range([0, this._width]);
      
    // create a labels layer
    this.layer("labels", this.base.append("g"), {
      dataBind: function(data) {
        var chart = this.chart();
        
        // set the domain of the xScale so we know 
        // how to map it. Give it a range of 10 on both
        // ends.
        chart.xScale.domain([
          Math.min.apply(null, data) - 10, 
          Math.max.apply(null, data) + 10 
        ]);
        return this.selectAll("text")
          .data(data); 
      },
      insert: function() {
         return this.append("text")
           .classed("label", true);
      },
      events: {
        enter: function() {
          var chart = this.chart();
           // position the labels at the mid height point
           return this.attr("y", chart.height()/2)
             .attr("x", function(d) {
               return chart.xScale(d);
             })
             .attr("text-anchor", "middle")
             .text(function(d) { return d; });
        }
      }
    });
  },
  width: function(newWidth) {
    if (arguments.length === 0) {
        return this._width;
    }
    this._width = newWidth;
    this.base.attr("width", this._width);
    this.xScale.range([0, this._width]);
    return this;
  },
  
  height: function(newHeight) {
    if (arguments.length === 0) {
        return this._height;
    } 
    this._height = newHeight;
    this.base.attr("height", this._height);
    return this;
  }
  
});

var labels = d3.select("#vis")
  .append("svg")
  .chart("LabelChart")
  .width(100)
  .height(100);

labels.draw([10,24,50,60,85]);

Now we also have a circle rendering chart that renders circles along the x axis:

circlesmixin

// define a new chart type: a circle chart
d3.chart("CircleChart", {

  initialize: function() {
    
    // setup some size defaults
    this._width = this._width   || this.base.attr("width") || 200;
    this._height = this._height || this.base.attr("height") || 200;
    
    // initialize a scale along which we will render 
    // the circles
    this.xScale = d3.scale.linear()
       .range([0, this._width]);
    
    // create a layer of circles that will go into
    // a new group element on the base of the chart
    this.layer("circles", this.base.append("g"), {

      // select the elements we wish to bind to and 
      // bind the data to them.
      dataBind: function(data) {
        var chart = this.chart();
        
        // set the domain of the xScale so we know 
        // how to map it. Give it a range of 10 on both
        // ends.
        chart.xScale.domain([
          Math.min.apply(null, data) - 10, 
          Math.max.apply(null, data) + 10 
        ]);
        return this.selectAll("circle")
          .data(data);
      },

      // insert actual circles
      insert: function() {
        return this.append("circle");
      },

      // define lifecycle events
      events: {

        // paint new elements, but set their radius to 0
        // and make them red
        "enter": function() {
          var chart = this.chart();
          return this.attr("cx", function(d) {
            return chart.xScale(d);
          })
          .attr("cy", 10)
          .attr("r", 0)
          .style("fill", "red");
        },
        // then transition them to a radius of 5 and change
        // their fill to blue
        "enter:transition": function() {
          var chart = this.chart();
          return this
            .delay(500)
            .attr("r", chart.radius())
            .style("fill", "yellow");
        }
      }
    });
  },
  radius: function(newradius) {
    if (arguments.length === 0) {
        return this._radius;
    }
    this._radius = newradius;
    return this;
  },
  
  width: function(newWidth) {
    if (arguments.length === 0) {
        return this._width;
    }
    this._width = newWidth;
    this.base.attr("width", this._width);
    this.xScale.range([0, this._width]);
    return this;
  },
  
  height: function(newHeight) {
    if (arguments.length === 0) {
        return this._height;
    } 
    this._height = newHeight;
    this.base.attr("height", this._height);
    return this;
  }
});

Now if we wanted to combine them like so:

yellowcircles

We can define a new chart that mixes the above two in. See the jsBin for this code:

d3.chart("LabeledCirclesChart", {
  initialize: function() {
    
    // setup some size defaults
    this._width = this._width   || this.base.attr("width") || 200;
    this._height = this._height || this.base.attr("height") || 100;
    
    this.circles = this.mixin("CircleChart", this.base.append("g"));
    this.labels =  this.mixin("LabelChart",  this.base.append("g"));
   
    // make the radius larger than the default
    this.circles.radius(10);
    
    // overlay the labels on top of the circles
    this.labels.layer("labels")
      .attr("transform", "translate(0,-" + (
        (this.height() / 2) - this.circles.radius()*1.3) 
        + ")");
    
    // move the circles to the mid point of the height
    this.circles.layer("circles").on("enter", function() {
      var chart = this.chart();
      return this.attr("y", chart.height()/2);
    });
    
  },
  width: function(newWidth) {
    if (arguments.length === 0) {
        return this._width;
    }
    this._width = newWidth;
    this.base.attr("width", this._width);
    this.circles.width(this._width);
    this.labels.width(this._width);
    return this;
  },
  
  height: function(newHeight) {
    if (arguments.length === 0) {
        return this._height;
    } 
    this._height = newHeight;
    this.base.attr("height", this._height);
    this.circles.height(this._height);
    this.labels.height(this._height);
    return this;
  }
});

// create an instance of the chart on a d3 selection
var chart = d3.select('#vis')
  .append("svg")
  .chart("LabeledCirclesChart")
  .height(100)
  .width(200);
  

// render it with some data
chart.draw([10,24,50,60,85]);
Clone this wiki locally