Skip to content
Irene Ros edited this page Feb 13, 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

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