Reduce coupling between chart components and delegate responsibility. Build components based on Mike Bostock's example
- Use mediator pattern for communication between components.
- Embrace method chaining.
- component should make no assumptions about which properties to use in the data. Instead, use accessor functions
- A component is function which returns inner function.
This inner function contains the accessible methods. - Dispatch custom events from each component using
d3.dispatch
. - Allow binding callbacks to component events using an
on
method. - Set parameters using methods instead of an object literal.
Should return value if not given value (getter/setter). - Use
d3.local
to save component instance state between renders. - Allow component to be called on either transition or selection.
- Allow component to be called on multiple or single selection.
this
should always point to a dom element.- Don't re-render unless explicitly called.
- Try to keep the core component slim.
function barChartWithTransitions() {
// outer scope available to public methods
var events = d3.dispatch('click');
var transitionDuration = 600; // default
var label = d3.local();
function chart(group) {
// scope available to all contexts
group.each(function(data) {
// context-specific scope
var context = d3.select(this),
transition;
// use transition if that's what was passed in, other wise create a new one
if (group instanceof d3.transition) {
transition = context.transition(group);
} else {
transition = context.transition().duration(o.animationDuration);
}
/* render chart */
})
}
// component-scope event listerner binding
chart.on = function(evt, callback) {
events.on(evt, callback);
return chart;
};
// component-scope getter/setter
chart.transitionDuration = function(value) {
if (!arguments.length) {return transitionDuration;}
transitionDuration = value;
return chart;
};
// context-specific getter/setter
chart.label = function(context, value) {
if (typeof value === 'undefined' ) {
return context.nodes().map(function(node) {return label.get(node);});
}
context.each(function() {label.set(this, _);});
return chart;
};
return chart;
}
var barchart = barChartWithTransitions()
.transitionDuration(400)
.on('click', function(d) {console.log(d.name + ' clicked');});
d3.select('#chart1')
.datum(someDataArray)
.call(barchart.label, 'Quarterly Profits')
.call(barchart);
- Donut Chart
- Pie segemnts animate when data is updated
- Resizable with animation
- Selected pie segment rotates to alignment angle
- Icons are shown for each segment
- Legend
- Legend highlights when item is selected
- Description is shown for selected segment
- Description is not shown if selected segment is not contained in the data
- All elements interact through mediator app.js
- ECMAScript20**
- Custom d3 bundles via rollup or similar
- Most of these interactions require re-rendering more components than may be necessary. While this is fine in this example, it may be worth considering less heavy-handed options.
- Directory Structure
- Yes, I know this chart probably isn't the best way to display this data, but that's not the point right now
- Manifesto / Overview
- Basic Donut, app.js
- Animation
- Legend and Description, events
- Rotation
- Icons
- Tests
- Refactoring / Retesting / Final thoughts