-
Notifications
You must be signed in to change notification settings - Fork 84
chart attach
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:
- one that paints rectangles of a certain height along an x axis
- 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.
Imagine we have the following charts:
A labels chart that renders labels along the x axis:
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:
// 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:
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]);