The base chart class.
This is the base class for charts built with KotoJS.
class KotoChartName extends Koto {
// your extend the koto class
}
Best Practices: It is our recommendation that you name space your charts by prefixing it with
Koto
so if you were making a bar chart, you might name itKotoBarChart
.
Here is where you will write most of the code to initialize
your chart by setting up static things like default configs, scales, and layers.
class KotoChartName extends Koto {
constructor(container){
super(container);
// initialize configs, scales, layers, ect...
}
}
Note: If you don’t have the luxury of authoring your chart in ES6 (ECMAScript 2015) class syntax, then you can accomplish the same thing by extending the base Koto class with it’s
extend
method and then passing in aninitialize
function. More info on that in theKoto.extend
docs.
A “hook” method that you may define to modify input data before it is used to draw the chart’s layers and attachments. It is passed in the data that was passed into the Koto.draw
method and must return an array of data that will be passed to each of your chart’s layers.
RETURNS: {array} the data that will be passed to chart’s layer’s draw methods.
transform(data) {
// do something with the data
return data.reverse();
}
}
Note: You will most likely never call this method directly, but rather include it as part of a chart definition, and then rely on KotoJS to invoke it when you draw the chart with
Koto.draw
.
A “hook” method that you may define to choose which mutation of the input data is sent to which of the attached charts (by name). This only applies to charts that use the { Koto#attach} method.
RETURNS: {array} the data that will be passed to attached chart (with given name) layer’s draw methods.
demux(name, data) {
if (name === 'BarChart') {
return data.forBarChart;
} else {
return data.forLineChart;
}
}
Note: You will most likely never call this method directly, but rather include it as part of a chart definition, and then rely on KotoJS to invoke it when you draw the chart with
Koto.draw
.
A “hook” method that you may define that will be called after Koto.transform
and before each of your layers’ draw
method will be called. This is a convenient place to setup chart attributes that are dependent on data (like scales).
preDraw(data) {
this.xScale.domain(
d3.extent(data, (row) => row.time)
);
}
Also an Event: A ‘preDraw’ event is called on your chart just AFTER this function is called in the case that you have something that needs to respond to that event that is outside of the chart itself.
Note: You will most likely never call this method directly, but rather include it as part of a chart definition, and then rely on KotoJS to invoke it when you draw the chart with
Koto.draw
.
A “hook” method that you may define that is called after all of your layers’ draw
method is called. This method does NOT wait for transitions on your layers to finish. If you need that functionality, then you should use the Koto.postTransition
hook.
postDraw(data) {
// do something once chart has rendered
}
Also an Event: A ‘postDraw’ event is called on your chart just AFTER this function is called in the case that you have something that needs to respond to that event that is outside of the chart itself.
Note: You will most likely never call this method directly, but rather include it as part of a chart definition, and then rely on KotoJS to invoke it when you draw the chart with
Koto.draw
.
A “hook” method that you may define that is called after all of your layers’ draw
method is called AND all of your layers’ transitions have finished.
postTransition(data){
// do something when all transitions have finished
}
Pro Tip: A ‘postDraw’ event is called on your chart just AFTER this function is called in the case that you have something that needs to respond to that event that is outside of the chart itself.
Note: You will most likely never call this method directly, but rather include it as part of a chart definition, and then rely on KotoJS to invoke it when you draw the chart with
Koto.draw
.
Interact with the chart’s Layers
The Layer.draw
method of attached layers will be invoked whenever this chart’s Kotot#draw
is invoked and will receive the data (optionally modified by the chart’s ` Koto#transform` method.
dataBind
and insert
methodIf only a name
is provided, simply return the layer registered to that name (if any).
If a name
and selection
are provided, treat the selection
as a previously-created layer and attach it to the chart with the specified name
.
If all three arguments are specified, initialize a new Layer using the specified selection
as a base passing along the specified options
.
class KotoChartName extends Koto {
constructor(container){
super(container);
var layerContainer = this.base.append('g');
var layer = this.layer('bars', layerContainer, {
dataBind: function (data) {
return this.selectAll('.bars').data(data);
},
insert: function () {
this.append('rect').classed('bars', true)
}
});
}
}
Removes a layer from the chart. This is usefull if you are extending a chart that has a layer that you don’t need.
RETURNS: {Layer} the layer that was removed from the chart
var layer = KotoChartName.unlayer('layerName');
// to reattach layer
KotoChartName.layer('newName', layer);
or this can be done from inside a chart’s constructor function
class KotoExtendedChart extends KotoChartName {
constructor(container){
super(container);
// remove the 'bars' layer
this.unlayer('bars');
}
}
Register or retrieve an “attachment” Chart. The “attachment” chart’s draw
method will be invoked whenever the containing chart’s draw
method is invoked.
If only name is provided, then it will return the attached chart with given name
If name and chart is provided then it will attach the given chart with given name and return the ‘parent chart’ instance (for chaining).
class KotoPieBars extends Koto {
constructor(container){
super(container);
// initialize composite charts
var barChart = new KotoBarChart(this.base.append('g'));
var pieChart = new KotPieChart(this.base.append('g'));
// attach composite charts
this.attach('bars', barChart);
this.attach('pie', pieChart);
}
}
Update the chart’s representation in the DOM, drawing all of its layers and any “attachment” charts (as attached via Koto#attach
).
var chart = new KotoChartName(d3.select('svg'));
chart.config({
height: 500,
width: 500,
fill: 'blue'
});
chart.draw([1,2,3,4,5,26,47,88]);
Note: The first time you call this method, the property
hasDrawn
on your chart will be set to true. This is helpful if you want to only run some code on the first time the chart is drawn.
Subscribe a callback function to an event triggered on the chart. See Kotot#once
to subscribe a callback function to an event for one occurrence.
There are a few preset events that Koto will fire as part of each chart’s lifecycle. You can also manually trigger custom events using the Koto.trigger
method.
var chart = new KotoChartName(d3.select('svg'));
chart.on('postTransition', function (data) {
// do something external to chart once the chart has been drawn
// and all of it's transitions are finished
positionLegend();
});
chart.draw([1,2,3,4,5,26,47,88]);
Subscribe a callback function to an event triggered on the chart. This function will be invoked at the next occurrence of the event and immediately unsubscribed. See Koto#on
to subscribe a callback function to an event indefinitely.
There are a few preset events that Koto will fire as part of each chart’s lifecycle. You can also manually trigger custom events using the Koto.trigger
method.
var chart = new KotoChartName(d3.select('svg'));
chart.once('postTransition', function (data) {
// do something external to chart once the chart has been drawn
// and all of it's transitions are finished
showTitle();
});
chart.draw([1,2,3,4,5,26,47,88]);
Unsubscribe one or more callback functions from an event triggered on the chart.
name
is specified, all handlers subscribed to that event will be unsubscribed.name
and callback
are specified, only that function will be unsubscribed from that event.name
and context
are specified (but callback
is omitted), all events bound to the given event with the given context will be unsubscribed.var chart = new KotoChartName(d3.select('svg'));
function action () {
chart.trigger('acted', 'an action occured!');
}
chart.on('acted', function (message) {
console.log('action: ', message);
});
// will trigger event
action();
chart.off();
// won't trigger event
action();
Publish an event on the chart with the given name
.
var chart = new KotoChartName(d3.select('svg'));
function action () {
chart.trigger('acted', 'an action occured!');
}
chart.on('acted', function (message) {
console.log('action: ', message);
});
// will trigger event
action();
Clean up chart and remove base node from DOM.
var chart = new KotoChartName(d3.select('svg'));
// when done with chart
chart.destory();
Getter / Setter for chart’s config options. This function operates similar to how d3’s attr
or style
functions operate.
var chart = new KotoChartName(d3.select('svg'));
// set config item by name
chart.config('height', 500);
// get config item
chart.config('height') // => 500;
// bulk set chart configs with object
chart.config({
height: 500,
width: 500
});
var chart = new KotoChartName(d3.select('svg'));
// set config by name with definition object
chart.config('height', {
displayName: 'Height',
description: 'The height of the chart',
type: 'number',
value: 500,
units: 'px'
});
// get config item
chart.config('height') // => 500;
Pro Tip: This method as been aliased as
Koto.c
for convenience.
Getter / Setter for chart’s data accessors. This function operates similar to how d3’s attr
or style
functions operate.
var chart = new KotoChartName(d3.select('svg'));
// set accessor by name
chart.accessor('name', function (row) { return row.name; });
// get accessor function by name (and use it)
var data = {name: 'koto', value; 100};
var name = chart.accessor('name')(data) // => 100;
Pro Tip: This method as been aliased as
Koto.a
for convenience.
We have exposed the internal Layer class so that it can be subclassed for reusing common layers such as a “tooltip” layer.
class TooltipLayer extends Koto.Layer {
// extend Layer class
otherMethod() {
return 'something';
}
}
const tooltipLayer = new TooltipLayer(selection, {
dataBind(data, context) {
return this.selectAll('rect').data(data);
},
insert() {
return this.append('rect');
}
});
chart.layer('Tooltip', tooltipLayer);
Pro Tip: You can use object destructuring to pull Layer out of Koto namespace
const { Layer } = Koto;
// you now have a variable Layer that is the Layer constructor.
This will extend a chart by passing in an initialization function or object with methods that you would like to overwrite from the base chart. Because the constructor method is a reserved method, we have renamed that option to initialize
.
RETURNS: {Chart} new, extended constructor function
var ExtendedChart = KotoChartName.extend(function () {
// this contex is the extended chart.
// initialize chart as you would in a construtor function
});
var chart = new ExtendedChart(d3.select('svg'));
var ExtendedChart = KotoChartName.extend({
initialize: function () { /* code */ },
transform: function () { /* code */ },
preDraw: function () { /* code */ }
});
var chart = new ExtendedChart(d3.select('svg'));
Pro Tip: This is useful if you don’t have the luxury using ES6 (ECMAScript 2015) class syntax for composing charts.
The layer class.
The Layer class is used internally by the Koto.layer
method to create and attach layers to charts. The selection
and options
parameters are passed into the layer constructor to create a new layer.
dataBind
method AND an insert
method. Optionally, you can include an ‘events’ object with keys that are the layer’s ‘life-cycle’ eventsReturns: Layer instance
// basic example
class KotoChartName extends Koto {
constructor(container){
super(container);
var layerContainer = this.base.append('g');
var layer = this.layer('bars', layerContainer, {
dataBind: function (data) {
return this.selectAll('.bars').data(data);
},
insert: function () {
this.append('rect').classed('bars', true)
}
});
// var layer is an instance of the 'Layer' class.
// because I did not pass the life-cycle events in as optoins,
// I'll have to use the layer's `on` method to define what do
// on those events. See `Layer.on`
layer.on('enter', function () { /* code */ })
.on('merge:transition', function () { /* code */ })
.on('exit', function () { /* code */ });
}
}
// example with 'life-cycle' events passed in as options
class KotoChartName extends Koto {
constructor(container){
super(container);
var layerContainer = this.base.append('g');
var layer = this.layer('bars', layerContainer, {
dataBind: function (data) {
return this.selectAll('.bars').data(data);
},
insert: function () {
this.append('rect').classed('bars', true)
},
events: {
'enter': function () { /* code */ },
'merge:transition': function () { /* code */ },
'exit': function () { /* code */ }
}
});
// var layer is an instance of the 'Layer' class.
}
}
Note: You will most likely not instantiate a new ‘Layer’ instance directly, you’ll use the
Koto.layer
method to create a new layer instance.
Create a layer using the provided base
selection.
base
d3.selection The containing DOM node for the layer.options
Object Overrides for databind, insert and event methods.
options.databind
Function databind overrideoptions.insert
Function insert overrideoptions.events
[Function] life-cycle event handler overrides.Possible values are [enter, update, merge, exit] with or without the ‘transition postfix’.This method is invoked by the Layer.draw
method which is invoked by the Koto.draw
method. The purpose of this function is to join data to the layer’s DOM nodes. This method must be overwritten for each layer instance.
The this
context of the function is the layers base selection.
// basic example
class KotoChartName extends Koto {
constructor(container){
super(container);
var layerContainer = this.base.append('g');
var layer = this.layer('bars', layerContainer, {
dataBind: function (data) {
// this is the base selection (layerContainer)
return this.selectAll('.bars').data(data);
},
insert: function () {
// this is the 'enter' selection of item returned by dataBind
this.append('rect').classed('bars', true)
}
});
}
}
This method is invoked by the Layer.draw
method which is invoked by the Koto.draw
method. The purpose of this function is to insert missing DOM nodes based on the data. This method must be overwritten for each layer instance.
The this
context of this function is the ‘enter’ selection of the ‘data bound’ selection returned by dataBind
method.
// basic example
class KotoChartName extends Koto {
constructor(container){
super(container);
var layerContainer = this.base.append('g');
var layer = this.layer('bars', layerContainer, {
dataBind: function (data) {
// this is the base selection (layerContainer)
return this.selectAll('.bars').data(data);
},
insert: function () {
// this is the 'enter' selection of item returned by dataBind
this.append('rect').classed('bars', true)
}
});
}
}
Subscribe a handler to a “lifecycle event”. These events (and only these events) are triggered when Layer#draw
is invoked. More information on ‘life-cycle events’ will follow.
// basic example
class KotoChartName extends Koto {
constructor(container){
super(container);
var layerContainer = this.base.append('g');
var layer = this.layer('bars', layerContainer, {
dataBind: function (data) {
return this.selectAll('.bars').data(data);
},
insert: function () {
this.append('rect').classed('bars', true)
}
});
// Because I did not pass the life-cycle events in as optoins,
// I'll have to use the layer's `on` method to define what do
// on those events. See `Layer.on`
layer.on('update', function () { /* code */ })
.on('enter', function () { /* code */ })
.on('merge:transition', function () { /* code */ })
.on('exit', function () { /* code */ });
}
}
Valid ‘life-cycle’ events are:
Also, subscribe handlers to a ‘life-cycle’ event’s transition selection by attaching ‘:transition’ to the event name. In other words, the following are also valid event names:
For more information on ‘life-cycle’ events see this blog (post.)[http://bost.ocks.org/mike/join/]
Unsubscribe the specified handler from the specified event. If no handler is supplied, remove all handlers from the event.
layer.on('enter', function () {
this.style({
fill: 'blue',
stroke: 'none'
});
});
// 'enter' event handler will execute
layer.draw(data);
layer.off('enter');
// 'enter' event handle WON'T execute
layer.draw(data);
Render the layer according to the input data:
Layer#dataBind
),Layer#insert
),Layer#on
) with the lifecycle selections.Note: You will probably never call this method directly but rely on the
Koto.draw
to call it.