For a time series visualization in d3, I want to highlight years on the axis. I've accomplished this by making my own xAxis renderer, which invokes the native axis
function and then implements my own custom logic to format the ticks that it renders.
This is how I've done it ( see working example on jsbin ):
xAxis = d3.svg.axis()
.scale(xScale)
customXAxis = function(){
xAxis(this);
d3.selectAll('.tick', this)
.classed("year", isYear);
};
...
xAxis.ticks(10);
xAxisElement = canvas.append("g")
.classed("axis x", true)
.call(customXAxis);
This gets the job done, but feels wrong; and it hasn't really extended the axis, it's only wrapped it. Ideally my customXAxis
would inherit the properties of d3's axis
component, so I would be able to do things like this:
customXAxis.ticks(10)
Thanks to @meetamit and @drakes for putting this together. Here's what I've ended up with: http://bl.ocks.org/HerbCaudill/ece2ff83bd4be586d9af
Yep, you can do all that. Following mbostock's suggestions here in conjunction with `d3.rebind' you get:
// This outer function is the thing that instantiates your custom axis.
// It's equivalent to the function d3.svg.axis(), which instantiates a d3 axis.
function InstantiateCustomXAxis() {
// Create an instance of the axis, which serves as the base instance here
// It's the same as what you named xAxis in your code, but it's hidden
// within the custom class. So instantiating customXAxis also
// instantiates the base d3.svg.axis() for you, and that's a good thing.
var base = d3.svg.axis();
// This is just like you had it, but using the parameter "selection" instead of
// the "this" object. Still the same as what you had before, but more
// in line with Bostock's teachings...
// And, because it's created from within InstantiateCustomXAxis(), you
// get a fresh new instance of your custom access every time you call
// InstantiateCustomXAxis(). That's important if there are multiple
// custom axes on the page.
var customXAxis = function(selection) {
selection.call(base);
// note: better to use selection.selectAll instead of d3.selectAll, since there
// may be multiple axes on the page and you only want the one in the selection
selection.selectAll('.tick', this)
.classed("year", isYear);
}
// This makes ticks() and scale() be functions (aka methods) of customXAxis().
// Calling those functions forwards the call to the functions implemented on
// base (i.e. functions of the d3 axis). You'll want to list every (or all)
// d3 axis method(s) that you plan to call on your custom axis
d3.rebind(customXAxis, base, 'ticks', 'scale');// etc...
// return it
return customXAxis;
}
To use this class, you just call
myCustomXAxis = InstantiateCustomXAxis();
You can now also call
myCustomXAxis
.scale(d3.scale.ordinal())
.ticks(5)
And of course the following will continue to work:
xAxisElement = canvas.append("g")
.classed("axis x", true)
.call(myCustomXAxis);
That's the idiomatic way to implement classes within d3. Javascript has other ways to create classes, like using the prototype
object, but d3's own reusable code uses the above method — not the prototype way. And, within that, d3.rebind
is the way to forward method calls from the custom class to what is essentially the subclass.
After a lot of code inspection and hacking, and talking with experienced d3 people, I've learned that d3.svg.axis()
is a function (not an object nor a class) so it can't be extended nor wrapped. So, to "extend" it we will create a new axis, run a selection on the base axis()
to get those tick marks selected, then copy over all the properties from the base axis()
in one fell swoop, and return this extended-functionality version.
var customXAxis = (function() {
var base = d3.svg.axis();
// Select and apply a style to your tick marks
var newAxis = function(selection) {
selection.call(base);
selection.selectAll('.tick', this)
.classed("year", isYear);
};
// Copy all the base axis methods like 'ticks', 'scale', etc.
for(var key in base) {
if (base.hasOwnProperty(key)) {
d3.rebind(newAxis, base, key);
}
}
return newAxis;
})();
customXAxis
now fully "inherits" the properties of d3's axis component. You can safely do the following:
customXAxis
.ticks(2)
.scale(xScale)
.tickPadding(50)
.tickFormat(dateFormatter);
canvas.append("g").call(customXAxis);
*With the help of @HerbCaudill's boilerplate code, and inspired by @meetamit's ideas.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.