d3.compose
d3.compose adds advanced chart layout and composition to d3 and d3.chart. Create rich, data-bound charts by composing charts (like Line and Bars) and components (like Axis, Title, and Legend).
Downloads & Dependencies
d3.compose-allIncludes all charts, components, extensions, and mixins. |
|
---|---|
d3.compose-all.js | 186kb Development: Full source with tons of comments. |
d3.compose-all.min.js | 15.2kb Production: minified and gzipped (Source Map) |
d3.compose.css | Base styling for d3.compose charts/components |
d3.compose-mixinsUse if you're creating your own charts/components and want to use d3.compose's mixins to help. |
|
d3.compose-mixins.js | 114kb Development |
d3.compose-mixins.min.js | 9.3kb Production (Source Map) |
d3.composeUse if you're using your own charts/components and don't need mixins, charts, or components provided by d3.compose. |
|
d3.compose.js | 84kb Development |
d3.compose.min.js | 7.5kb Production (Source Map) |
d3.compose depends on the following libraries:
Introduction
When creating complex charts with D3.js and d3.chart, laying out and sizing parts of the chart are often manual processes and creating composable and reusable components becomes very difficult. d3.compose creates a standard base that charts and components are built on top of that automatically handles layout and sizing and can be composed and layered to created rich, data-bound charts.
Usage
Add d3.compose and its dependencies to your page:
Compose
Compose rich, data-bound charts from charts (like Lines and Bars) and components (like Axis, Title, and Legend) with d3 and d3.chart.
Using the options
property, charts and components can be bound to data and customized to create dynamic charts.
<div id="#chart"></div>
var chart = d3.select('#chart').chart('Compose', function(data) {
// Process data...
// Create shared scales
var scales = {
x: {data: data.input, key: 'x', adjacent: true},
y: {data: data.input, key: 'y'},
y2: {data: data.output, key: 'y'}
};
// Setup charts and components
var charts = [
d3c.bars('input', {data: data.input, xScale: scales.x, yScale: scales.y}),
d3c.lines('output', {data: data.output, xScale: scales.x, yScale: scales.y2})
];
var title = d3c.title('d3.compose');
var xAxis = d3c.axis('xAxis', {scale: scales.x});
var yAxis = d3c.axis('yAxis', {scale: scales.y});
var y2Axis = d3c.axis('y2Axis', {scale: scales.y2});
// Layout charts and components
return [
title,
[yAxis, d3c.layered(charts), y2Axis],
xAxis
];;
});
chart.draw({input: [...], output: [...]});
options
{Function|Object}
Get/set the options object/function
for the chart that takes data
and
returns [...layout]
for composing child charts and components.
// get
chart.options();
// set (static)
chart.options([
// ...
]);
// set (dynamic, takes data and returns options)
chart.options(function(data) {
// process data...
return [
// ...
];
});
// Set directly from d3.chart creation
d3.select('#chart')
.chart('Compose', function(data) {
// ...
});
margins
{Object {top, right, bottom, left}} [{top: 10, right: 10, bottom: 10, left: 10}]
Margins between edge of container and components/chart
chart.margins({top: 10, right: 20, bottom: 10, left: 20});
width
{Number}
Get/set overall width of chart
height
{Number}
Get/set overall height of chart
responsive
{Boolean} [true]
Enable responsive container + viewBox so that chart scales to fill width (only works if selection is not an svg)
charts
{Array}
Set charts from options or get chart instances.
Each chart should use a unique key so that updates are passed to the existing chart
(otherwise they are recreated on update).
The type
option must be a registered d3.chart
and all other options are passed to the chart.
chart.charts([
{id: 'input', type: 'Bars'}, // options to pass to Bars chart
{id: 'output', type: 'Lines'} // options to pass to Lines chart
]);
components
{Array}
Set components from options or get components instances.
Each component should use a unique key so that updates are passed to the existing chart
(otherwise they are recreated on update).
The type
option must be a registered d3.chart
and all other options are passed to the component.
chart.components([
{id: 'axis.y', type: 'Axis'}, // options to pass to Axis component
{id: 'title', type: 'Title'} // options to pass to Title component
])
delay
{Number|Function} [d3 default: 0]
Delay start of transition by specified milliseconds. (applied to all charts and components as default)
duration
{Number|Function} [d3 default: 250ms]
Transition duration in milliseconds. (applied to all charts and components as default)
ease
{String|Function} [d3 default: 'cubic-in-out']
Transition ease function. (applied to all charts and components as default)
- See: Transitions#ease
- Note: arguments to pass to
d3.ease
are not supported
draw
draw(data)
-
data
(Any)
Draw chart with given data
var chart = d3.select('#chart')
.chart('Compose', function(data) {
// ...
});
chart.draw([1, 2, 3]);
chart.draw({values: [1, 2, 3]});
chart.draw([
{values: [1, 2, 3]},
{values: [4, 5, 6]}
]);
redraw
Redraw chart with current data
Inherits
Chart
Common base for creating charts.
Standard d3.chart
charts can be used with d3.compose, but extending d3.chart('Chart')
includes helpers for properties and “di” functions.
Extending
To take advantage of “di”-binding (automatically injects chart
into “di” methods)
and automatically setting properties from options
, use d3.compose.helpers.di
and d3.compose.helpers.property
when creating your chart.
var helpers = d3.compose.helpers;
d3.chart('Chart').extend('Pie', {
initialize: function() {
// same as d3.chart
},
transform: function(data) {
// same as d3.chart
},
color: helpers.di(function(chart, d, i) {
// "di" function with parent chart injected ("this" = element)
}),
centered: helpers.property({
default_value: true
// can be automatically set from options object
})
});
z_index Static
Default z-index for chart (Components are 50 by default, so Chart = 100 is above component by default)
d3.chart('Chart').extend('BelowComponentLayers', {
// ...
}, {
z_index: 40
});
Inherits
Component
Common base for creating components that includes helpers for positioning and layout.
Extending
d3.chart('Component')
contains intelligent defaults and there are no required overrides.
Create a component just like a chart, by creating layers in the initialize
method in extend
.
- To adjust layout calculation, use
prepareLayout
,getLayout
, andsetLayout
. - To layout a component within the chart, use
skip_layout: true
and the staticlayer_type: 'chart'
d3.chart('Component').extend('Key', {
initialize: function() {
this.layer('Key', this.base, {
dataBind: function(data) {
return this.selectAll('text')
.data(data);
},
insert: function() {
return this.append('text');
},
events: {
merge: function() {
this.text(this.chart().keyText)
}
}
})
},
keyText: helpers.di(function(chart, d, i) {
return d.abbr + ' = ' + d.value;
})
});
position
{String} ['top']
Component’s position relative to chart (top, right, bottom, left)
width
{Number} [(actual width)]
Get/set the width of the component (in pixels) (used in layout calculations)
height
{Number} [(actual height)]
Get/set the height of the component (in pixels) (used in layout calculations)
margins
{Object} [{top: 0, right: 0, bottom: 0, left: 0}]
Margins (in pixels) around component
centered
{Boolean} [false]
Center the component vertically/horizontally (depending on position)
skip_layout
Skip component during layout calculations and positioning (override in prototype of extension)
d3.chart('Component').extend('NotLaidOut', {
skip_layout: true
});
prepareLayout
prepareLayout(data)
-
data
(Any)
Perform any layout preparation required before getLayout (default is draw) (override in prototype of extension)
Note: By default, components are double-drawn; for every draw, they are drawn once to determine the layout size of the component and a second time for display with the calculated layout. This can cause issues if the component uses transitions. See Axis for an example of a Component with transitions.
d3.chart('Component').extend('Custom', {
prepareLayout: function(data) {
// default: this.draw(data);
// so that getLayout has real dimensions
// -> custom preparation (if necessary)
}
})
getLayout
getLayout(data)
-
data
(Any)
position, width, and height for layout
Get layout details for use when laying out component (override in prototype of extension)
d3.chart('Component').extend('Custom', {
getLayout: function(data) {
var calculated_width, calculated_height;
// Perform custom calculations...
// Must return position, width, and height
return {
position: this.position(),
width: calculated_width,
height: calculated_height
};
}
});
setLayout
setLayout(x, y, options)
-
x
(Number)position of base top-left
-
y
(Number)position of base top-left
-
options
(Object)-
[height]
(Object)height of component in layout
-
[width]
(Object)width of component in layout
-
Set layout of underlying base (override in prototype of extension)
d3.chart('Component').extend('Custom', {
setLayout: function(x, y, options) {
// Set layout of this.base...
// (the following is the default implementation)
var margins = this.margins();
// (handle this.centered())
this.base
.attr('transform', helpers.translate(x + margins.left, y + margins.top));
this.height(options && options.height);
this.width(options && options.width);
}
});
z_index Static
Default z-index for component (Charts are 100 by default, so Component = 50 is below chart by default)
d3.chart('Component').extend('AboveChartLayers', {
// ...
}, {
z_index: 150
});
layer_type Static
Set to 'chart'
to use chart layer for component.
(e.g. Axis uses chart layer to position with charts, but includes layout for ticks)
d3.chart('Component').extend('ChartComponent', {
// ...
}, {
layer_type: 'chart'
});
Inherits
Overlay
Common base for creating overlays that includes helpers for positioning and show/hide.
Extending
Create an overlay just like a chart, by creating layers in the initialize
method in extend
.
- To adjust positioning, override
position
- To adjust show/hide behavior, override
show
/hide
d3.chart('Overlay').extend('ClosestPoints', {
// TODO
});
x
{Number} [0]
Overlay’s top-left x-position in px from left
y
{Number} [0]
Overlay’s top-left y-position in px from top
hidden
{Boolean} [true]
Whether overlay is currently hidden
style
{String} [set from x, y, and hidden]
Overlays base styling (default includes position and hidden)
position
position(position, [y])
-
position
(Object|Number){x,y}, {container: {x,y}}, {chart: {x,y}} or x in px from left
-
[y]
(Number)in px from top
Position overlay layer at given x,y coordinates
// Absolute, x: 100, y: 50
overlay.position(100, 50);
overlay.position({x: 100, y: 50});
// Relative-to-chart, x: 50, y: 40
overlay.position({chart: {x: 50, y: 40}});
// Relative-to-container, x: 75, y: 50
overlay.position({container: {x: 75, y: 50}});
show
Show overlay (with display: block
)
hide
Hide overlay (with display: none
)
getAbsolutePosition
getAbsolutePosition(container_position)
-
container_position
(Object)({x, y})
absolute {x, y} relative to container div
Get absolute position from container position (needed since container position uses viewBox and needs to be scaled to absolute position)
Inherits
Base
Shared functionality between all charts and components.
- Set properties automatically from
options
, - Store fully transformed data
- Adds
"before:draw"
and"draw"
events - Standard
width
andheight
calculations
data
{Any}
Store fully-transformed data for direct access from the chart
options
{Object}
Overall options for chart/component, automatically setting any matching properties.
var property = d3.compose.helpers.property;
d3.chart('Base').extend('HasProperties', {
a: property(),
b: property({
set: function(value) {
return {
override: value + '!'
};
}
})
});
var instance = d3.select('#chart')
.chart('HasProperties', {
a: 123,
b: 'Howdy',
c: true
});
// Equivalent to:
// d3.select(...)
// .chart('HasProperties')
// .options({...});
console.log(instance.a()); // -> 123
console.log(instance.b()); // -> Howdy!
console.log(instance.options().c); // -> true
width
Get width of this.base
.
(Does not include set
for setting width of this.base
)
height
Get height of this.base
.
(Does not include set
for setting height of this.base
)
Lines
Create an XY Lines chart with single or series data.
Extending
Great care has been taken in making the standard charts in d3.compose extensible.
To extend the Lines
chart, the following methods are available:
createLine
lineKey
lineData
onDataBind
onInsert
onEnter
onEnterTransition
onUpdate
onUpdateTransition
onMerge
onMergeTransition
onExit
onExitTransition
View the Lines.js
source for the default implementation and more information on these methods.
var chart = d3.select('#chart').chart('Compose', function(data) {
return {
charts: {
input: {
type: 'Lines'
data: data.input,
// xScale: ...,
// yScale: ...,
// other properties...
}
}
};
});
// Single y-values
chart.draw([1, 2, 3]);
// Series (x,y) values
chart.draw([
{values: [{x: 0, y: 1}, {x: 1, y: 2}, {x: 2, y: 3}]}
{values: [{x: 0, y: 3}, {x: 1, y: 2}, {x: 2, y: 1}]}
]);
interpolate
{String} [monotone]
Set interpolation mode for line
- See: SVG-Shapes#line_interpolate
- Set to
null
or'linear'
for no interpolation
Inherits
Bars
Bars chart with centered or adjacent display for single or series data.
To display bars for different series next to each other (adjacent),
use the adjacent
option when creating the xScale
(see example below).
Extending
To extend the Bars
chart, the following methods are available:
barHeight
barWidth
barX
barY
barClass
onDataBind
onInsert
onEnter
onEnterTransition
onUpdate
onUpdateTransition
onMerge
onMergeTransition
onExit
onExitTransition
var chart = d3.select('#chart').chart('Compose', function(data) {
// Display bars for different series next to each other (adjacent: true)
var xScale = {type: 'ordinal', adjacent: true, domain: [0, 1, 2]};
return {
charts: {
output: {
type: 'Bars',
data: data.output,
xScale: xScale,
// yScale: ...,
// other properties...
}
}
};
});
// Single y-values
chart.draw([10, 20, 30]);
// Series (x,y) values
chart.draw([
{values: [{x: 0, y: 10}, {x: 1, y: 20}, {x: 2, y: 30}]},
{values: [{x: 0, y: 30}, {x: 1, y: 20}, {x: 2, y: 10}]}
]);
Inherits
StackedBars
Bars chart with values stacked on top of each other
(See Bars
for extensibility details)
var chart = d3.select('#chart').chart('Compose', function(data) {
// Display bars for different series next to each other (adjacent: true)
var xScale = {type: 'ordinal', adjacent: true, domain: [0, 1, 2]};
return {
charts: {
stacked_output: {
type: 'StackedBars',
data: data.output,
xScale: xScale,
// yScale: ...,
// other properties...
}
}
};
});
// Single y-values
chart.draw([10, 20, 30]);
// Series (x,y) values
chart.draw([
{values: [{x: 0, y: 10}, {x: 1, y: 20}, {x: 2, y: 30}]},
{values: [{x: 0, y: 30}, {x: 1, y: 20}, {x: 2, y: 10}]}
]);
Inherits
HorizontalBars
Bars chart with bars that group from left-to-right
(See Bars
for extensibility details)
var chart = d3.select('#chart').chart('Compose', function(data) {
// Display bars for different series next to each other (adjacent: true)
var xScale = {type: 'ordinal', adjacent: true, domain: [0, 1, 2]};
return {
charts: {
output: {
type: 'HorizontalBars',
data: data.output,
xScale: xScale,
// yScale: ...,
// other properties...
}
}
};
});
// Single y-values
chart.draw([10, 20, 30]);
// Series (x,y) values
chart.draw([
{values: [{x: 0, y: 10}, {x: 1, y: 20}, {x: 2, y: 30}]},
{values: [{x: 0, y: 30}, {x: 1, y: 20}, {x: 2, y: 10}]}
]);
Inherits
HorizontalStackedBars
Horizontal Stacked Bars
(See Bars
for extensibility details)
var chart = d3.select('#chart').chart('Compose', function(data) {
// Display bars for different series next to each other (adjacent: true)
var xScale = {type: 'ordinal', adjacent: true, domain: [0, 1, 2]};
return {
charts: {
output: {
type: 'HorizontalStackedBars',
data: data.output,
xScale: xScale,
// yScale: ...,
// other properties...
}
}
};
});
// Single y-values
chart.draw([10, 20, 30]);
// Series (x,y) values
chart.draw([
{values: [{x: 0, y: 10}, {x: 1, y: 20}, {x: 2, y: 30}]},
{values: [{x: 0, y: 30}, {x: 1, y: 20}, {x: 2, y: 10}]}
]);
Inherits
Labels
Standalone or “embeddable” labels (uses mixins.Labels
and attachLabels
to embed in chart)
Extending
To extend the Labels
chart, the following methods are available:
insertLabels
mergeLabels
layoutLabels
transitionLabels
onDataBind
onInsert
onEnter
onEnterTransition
onUpdate
onUpdateTransition
onMerge
onMergeTransition
onExit
onExitTransition
View the Labels.js
source for the default implementation and more information on these methods.
var chart = d3.select('#chart').chart('Compose', function(data) {
return {
charts: {
input: {
type: 'Lines',
data: data.input,
// xScale, yScale, other properties...
// Show labels with default properties
labels: true
},
output: {
type: 'Bars',
data: data.output,
// xScale, yScale, other properties...
// Pass options to labels
labels: {
offset: 2,
position: 'top',
style: {
'font-size': '14px'
},
format: d3.format(',0d')
}
},
labels: {
type: 'Labels',
data: data.labels,
// xScale, yScale, other properties...
}
}
};
});
chart.draw({
input: [1, 2, 3],
output: [10, 20, 30],
labels: [
{x: 0, y: 0},
{x: 0, y: 30, label: 'Override (y by default)'},
{x: 2, y: 0},
{x: 2, y: 30}
]
});
format
{String|Function}
Formatting function or string (string is passed to d3.format
) for label values
position
{String|Function} [top|bottom]
Label position relative to data point (top, right, bottom, or left)
Additionally, (a)|(b)
can be used to set position to a
if y-value >= 0 and b
otherwise,
where a
and b
are top
, right
, bottom
, or left
For more advanced positioning, a “di” function can be specified to set position per label
labels.position('top'); // top for all values
labels.position('top|bottom'); // top for y-value >= 0, bottom otherwise
labels.position(function(d, i) { return d.x >= 0 ? 'right' : 'left'; });
offset
{Number|Object} [0]
Offset between data point and label
(if Number
is given, offset is set based on position)
padding
{Number} [1]
Padding between text and label background
anchor
{String} [middle]
Define text anchor (start, middle, or end)
(set by default based on label position)
alignment
{String} [middle]
Define text-alignment (top, middle, or bottom)
(set by default based on label position)
labelText
labelText(d, i)
-
d
(Any) -
i
(Number)
Get label text for data-point (uses “label” property or y-value)
labelClass
labelClass(d, i)
-
d
(Any) -
i
(Number)
Get class for label group
Inherits
Axis
Axis component for XY data (wraps d3.axis
).
Available d3.axis extensions:
ticks
tickValues
tickSize
innerTickSize
outerTickSize
tickPadding
tickFormat
d3.select('#chart')
.chart('Compose', function(data) {
var scales = {
x: {data: data, key: 'x'},
y: {data: data, key: 'y'}
};
var charts = [];
var xAxis = d3c.axis({scale: scales.x});
var yAxis = d3c.axis({scale: scales.y});
return [
// Display y-axis to left of charts
[yAxis, d3c.layered(charts)],
// Display x-axis below charts
xAxis
];
});
scale
{Object|d3.scale}
Scale to pass to d3.axis
- If
xScale
/yScale
are given,scale
is set automatically based onposition
. - Can be
d3.scale
or, ifObject
is given,helpers.createScale
is used
// Set with d3.scale directly
axis.scale(d3.scale());
// or with Object passed helpers.createScale
axis.scale({data: data, key: 'x'});
// For x0/y0 position, both xScale and yScale needed
// (scale is automatically set by position)
axis.xScale({domain: [0, 100]});
axis.yScale({domain: [0, 10]});
axis.position('y0');
// -> axis.scale() -> axis.xScale by default
translation
{Object} [(set based on position)]
{x,y} translation of axis relative to chart (set by default based on position)
orient
{String} [(set based on position)]
Axis orient for ticks (set by default based on position)
orientation
{String} [(set based on position)]
Axis orientation (vertical or horizonal)
gridlines
{Boolean|Object} [false]
Attach gridlines for axis
(true
to show with default options, {...}
to pass options to Gridlines
)
Inherits
Legend
Legend component that can automatically pull chart and series information from d3.compose
Notes:
- To exclude a chart from the legend, use
exclude_from_legend = true
in chart prototype or options - To exclude a series from the legend, use
exclude_from_legend = true
in series object - To add swatch for custom chart, use
Legend.registerSwatch()
d3.select('#chart')
.chart('Compose', function(data) {
var input = [{key: 'input', name: 'Input', values: data.input}];
var output = [
{key: 'output1', name: 'Output 1', values: data.output1},
{key: 'output2', name: 'Output 2', values: data.output2}
];
var charts = [
d3c.lines('a', {data: input}), // ...
d3c.bars('b', {data: output}) // ...
];
var legend = d3c.legend({charts: ['a', 'b']});
return [
[d3c.layered(charts), legend]
];
});
// -> automatically creates legend from series data for 'a' and 'b'
// (Lines Swatch) Input
// (Bars Swatch) Output 1
// (Bars Swatch) Output 2
// or, manually set data for legend
return [
d3c.legend({
data: [
{type: 'Lines', text: 'Input', class: 'series-index-0'},
{type: 'Bars', text: 'Output 1', class: 'series-index-0'},
{type: 'Bars', text: 'Output 2', class: 'series-index-1'},
]
})
};
charts
{Array}
Array of chart keys from container to display in legend
d3.select('#chart')
.chart('Compose', function(data) {
var charts = [
{id: 'a'},
{id: 'b'},
{id: 'c'}
];
var legend = d3c.legend({charts: ['a', 'c']});
return [
[d3c.layered(charts), legend]
];
});
swatchDimensions
{Object} [{width: 20, height: 20}]
Dimensions of “swatch” in px
margins
{Object} [{top: 8, right: 8, bottom: 8, left: 8}]
Margins (in pixels) around legend
stackDirection
{String} [(based on position)]
Direction to “stack” legend, “vertical” or “horizontal”. (Default is set based on position: top/bottom = “horizontal”, left/right = “vertical”)
registerSwatch
registerSwatch(types, create)
Static
-
types
(Array|String)Chart type(s)
-
create
(Function)“di” function that inserts swatch
Register a swatch create function for the given chart type
d3.chart('Legend').registerSwatch(['Lines'], function(chart, d, i) {
var dimensions = chart.swatchDimensions();
return this.append('line')
.attr('x1', 0).attr('y1', dimensions.height / 2)
.attr('x2', dimensions.width).attr('y2', dimensions.height / 2)
.attr('class', 'chart-line');
});
Inherits
InsetLegend
Legend positioned within chart bounds.
translation
{Object {x,y}} [{x: 10, y: 10, relative_to: 'left-top'}]
Position legend within chart layer {x, y, relative_to}
Use relative_to
to use x,y values relative to x-y origin
(e.g. "left-top"
is default)
d3.select('#chart')
.chart('Compose', function(data) {
return {
components: {
legend: {
type: 'InsetLegend',
// Position legend 10px away from right-bottom corner of chart
translation: {x: 10, y: 10, relative_to: 'right-bottom'}
}
}
}
});
Inherits
Text
Add text to a chart.
d3.select('#chart')
.chart('Compose', function(data) {
return {
components: {
title: {
type: 'Text',
position: 'top'
text: 'Main Title',
textAlign: 'left',
'class': 'title'
},
notes: {
type: 'Text',
position: 'bottom',
text: 'Notes',
'class': 'notes'
}
}
};
});
text
{String}
Text to display
rotation
{Number} [0]
Rotation of text
textAlign
{String} ["center"]
Horizontal text-alignment of text ("left"
, "center"
, or "right"
)
anchor
{String} [(set by `textAlign`)]
text-anchor for text ("start"
, "middle"
, or "end"
)
verticalAlign
{String} ["middle"]
Vertical aligment for text ("top"
, "middle"
, "bottom"
)
style
{Object} [{}]
Style object containing styles for text
Inherits
Title
Title component that extends Text with defaults (styling, sensible margins, and rotated when positioned left or right)
margins
{Object} [(set based on `position`)]
Margins (in pixels) around Title
rotation
{Number} [(set based on `position`)]
Rotation of title. (Default is -90
for position = "right"
, 90
for position = "left"
, and 0
otherwise).
Inherits
AxisTitle
Axis title component that extends Title with defaults (styling)
margins
{Object} [(set based on `position`)]
Margins (in pixels) around axis title
Inherits
Gridlines
Gridlines component that draws major ticks for chart
Uses d3.axis extensions for ticks:
ticks
tickValues
Extending
To extend the Gridlines
component, the following methods are available
onInsert
onEnter
onEnterTransition
onUpdate
onUpdateTransition
onMerge
onMergeTransition
onExit
onExitTransition
d3.select('#chart').chart('Compose', function(data) {
var scales = {
x: {data: data, key: 'x'},
y: {data: data, key: 'y'}
};
var vertical = d3c.gridlines({
scale: scales.x,
orientation: 'vertical'
});
var horizontal = d3c.gridlines({
scale: scales.y,
orientation: 'horizontal'
});
return [
vertical,
horizontal
];
});
orientation
{String} [horizontal]
Use horizontal, vertical gridlines
scale
{Object|d3.scale}
Scale to use for gridlines.
Can be d3.scale
or, if Object
is given, helpers.createScale
is used.
// Set with d3.scale directly
gridlines.scale(d3.scale());
// or with Object passed to helpers.createScale
gridlines.scale({data: data, key: 'x'});
helpers
d3.compose.helpers
includes general purpose helpers that are used throughout d3.compose.
Includes convenience functions for create charts/components (property
, di
, and mixin
),
helpful calculations (dimensions
, max
, and min
) and other common behavior.
createScale
createScale(options)
-
options
(Object|Function)(passing in
Function
returns original function with no changes)-
[type = 'linear']
(String)Any available
d3.scale
("linear"
,"ordinal"
,"log"
, etc.) or"time"
-
[domain]
(Array)Domain for scale
-
[range]
(Array)Range for scale
-
[data]
(Any)Used to dynamically set domain (with given value or key)
-
[value]
(Function)“di”-function for getting value for data
-
[key]
(String)Data key to extract value
-
[centered]
(Boolean)For “ordinal” scales, use centered x-values
-
[adjacent]
(Boolean)For “ordinal” + centered, set x-values for different series next to each other
- Requires series-index as second argument to scale, otherwise centered x-value is used
- Requires “data” or “series” options to determine number of series
-
[series]
(Number)Used with “adjacent” if no “data” is given to set series count
-
[padding = 0.1]
(Number)For “ordinal” scales, set padding between different x-values
-
[...]
(Array...)Set any other scale properties with array of arguments to pass to property
-
Create scale from options
// Simple type, range, and domain
var scale = createScale({
type: 'linear',
domain: [0, 100],
range: [0, 500]
});
// Calculate domain for data
var scale = createScale({
type: 'log',
data: [{y: 1}, {y: 100}, {y: 2000}, {y: 5000}],
key: 'y'
});
// Scale is passed through
var original = d3.scale.linear();
var scale = createScale(original);
scale === original;
// Set other properties by passing in "arguments" array
var scale = createScale({
type: 'ordinal',
domain: ['a', 'b', 'c', 'd', 'e'],
rangeRoundBands: [[0, 100], 0.1, 0.05] // -> rangeRoundBands([0, 100], 0.1, 0.05)
});
// Use ordinal + adjacent for bar charts
var scale = createScale({
type: 'ordinal',
adjacent: true,
domain: ['a', 'b', 'c'],
series: 2 // Series count is required for adjacent if data isn't given
})
dimensions
dimensions(selection)
-
selection
(d3.Selection)
{width, height}
Helper for robustly determining width/height of given selector. Checks dimensions from css, attributes, and bounding box.
mixin
mixin(Parent, mixins)
-
Parent
(Function) -
mixins
(...Object)
Combine mixins with Parent super class for extension
var a = {transform: function() {}, a: 1};
var b = {initialize: function() {}, b: 2};
var c = {c: 3};
var Custom = mixin(Chart, a, b, c).extend({
initialize: function(options) {
this._super.initialize.call(this, options);
// d
},
transform: function(data) {
data = this._super.transform.call(this, data);
// d
}
});
// initialize: Chart, b, d
// transform: Chart, a, d
property
property([options])
-
[options]
(Object)-
[default_value]
(Any)default value for property (when set value is
undefined
). If default value is a function, wrap in another function as default_value is evaluated by default. -
[get]
(Function)function(value) {return ...}
getter, wherevalue
is the stored value and return desired value -
[set]
(Function)function(value, previous) {return {override, after}}
. Returnoverride
to override stored value andafter()
to run after set -
[context = this]
(Object)context to evaluate get/set/after functions
-
()
: get, (value)
: set
Helper for creating properties for charts/components
var Custom = d3.chart('Chart').extend('Custom', {
// Create property that's stored internally as 'simple'
simple: property()
});
var custom; // = new Custom(...);
// set
custom.simple('Howdy');
// get
console.log(custom.simple()); // -> 'Howdy'
// Advanced
// --------
// Default values:
Custom.prototype.message = property({
default_value: 'Howdy!'
});
console.log(custom.message()); // -> 'Howdy!'
custom.message('Goodbye');
console.log(custom.message()); // -> 'Goodbye'
// Set to undefined to reset to default value
custom.message(undefined);
console.log(custom.message()); // -> 'Howdy!'
// Computed default value:
Custom.property.computed = property({
default_value: function() {
// "this" = Custom instance
return this.message();
}
});
// Function default value:
// For function default_values, wrap in function to differentiate from computed
Custom.property.fn = property({
default_value: function() {
return function defaultFn() {};
}
// The following would be incorrectly evaluated
// default_value: function defaultFn() {}
})
// Custom getter:
Custom.prototype.exclaimed = property({
get: function(value) {
// Value is the underlying set value
return value + '!';
}
});
custom.exclaimed('Howdy');
console.log(custom.exclaimed()); // -> 'Howdy!'
// Custom setter:
Custom.prototype.feeling = property({
set: function(value, previous) {
if (value == 'Hate') {
// To override value, return Object with override specified
return {
override: 'Love',
// To do something after override, use after callback
after: function() {
console.log('After: ' + this.feeling()); // -> 'After: Love'
}
};
}
}
custom.feeling('Hate'); // -> 'After: Love'
console.log(custom.feeling()); // -> 'Love'
});
stack
stack([options])
-
[options]
(Object)-
[direction = vertical]
(String)"vertical"
or"horizontal"
-
[origin]
(String)"top"
,"right"
,"bottom"
, or"left"
(by default,"top"
for"vertical"
and"left"
for"horizontal"
) -
[padding = 0]
(Number)padding (in px) between elements
-
[min_height = 0]
(Number)minimum spacing height (for vertical stacking)
-
[min_width = 0]
(Number)minimum spacing width (for horizontal stacking)
-
Stack given array of elements vertically or horizontally
// Stack all text elements vertically, from the top, with 0px padding
d3.selectAll('text').call(helpers.stack())
// Stack all text elements horizontally, from the right, with 5px padding
d3.selectAll('text').call(helpers.stack({
direction: 'horizontal',
origin: 'right',
padding: 5
}));
translate
translate([x], [y])
-
[x]
(Number|Object)value or
{x, y}
-
[y]
(Number)
Translate by (x, y) distance
translate(10, 15) == 'translate(10, 15)'
translate({x: 10, y: 15}) == 'translate(10, 15)'
rotate
rotate(degrees, [center = {x: 0, y: 0}])
-
degrees
(Number) -
[center = {x: 0, y: 0}]
(Object)
Rotate by degrees, with optional center
alignText
alignText(element, [line_height])
-
element
(Element) -
[line_height]
(Number)
offset
Find vertical offset to vertically align text
(needed due to lack of alignment-baseline
support in Firefox)
var label = d3.select('text');
// Place label vertically so that origin is top-left
var offset = alignText(label);
label.attr('transform', translate(0, offset));
// Center label for line-height of 20px
var offset = alignText(label, 20);
label.attr('transform', translate(0, offset));
isSeriesData
isSeriesData(data)
-
data
(Array)
Determine if given data is likely series data
max
max(data, getValue)
-
data
(Array) -
getValue
(Function)di function that returns value for given (d, i)
Get max for array/series by value di
var data = [
{values: [{y: 1}, {y: 2}, {y: 3}]},
{values: [{y: 4}, {y: 2}, {y: 0}]}
];
max(data, function(d, i) { return d.y; }); // -> 4
min
min(data, getValue)
-
data
(Array) -
getValue
(Function)di function that returns value for given (d, i)
Get min for array/series by value di
var data = [
{values: [{x: 1}, {x: 2}, {x: 3}]},
{values: [{x: 4}, {x: 2}, {x: 0}]}
];
min(data, function(d, i) { return d.x; }); // -> 0
getMargins
getMargins(margins, default_margins)
-
margins
(Number|Object) -
default_margins
(Object)
Get formatted margins for varying input
getMargins(4);
// -> {top: 4, right: 4, bottom: 4, left: 4}
getMargins({top: 20}, {top: 8, bottom: 8});
// -> {top: 20, right: 0, bottom: 8, left: 0}
di
di(callback)
-
callback
(Function)with
(chart, d, i)
arguments
Create wrapped (d, i)
function that adds chart instance as first argument.
Wrapped function uses standard d3 arguments and context.
Note: in order to pass proper context to di-functions called within di-function
use .call(this, d, i)
(where “this” is d3 context)
d3.chart('Base').extend('Custom', {
initialize: function() {
this.base.select('point')
.attr('cx', this.x);
// -> (d, i) and "this" used from d3, "chart" injected automatically
},
x: di(function(chart, d, i) {
// "this" is standard d3 context: node
return chart.xScale()(chart.xValue.call(this, d, i));
})
// xScale, xValue...
});
getParentData
getParentData(element)
-
element
(Element)
Get parent data for element (used to get parent series for data point)
var data = [{
name: 'Input',
values: [1, 2, 3]
}];
d3.selectAll('g')
.data(data)
.enter().append('g')
.selectAll('text')
.data(function(d) { return d.values; })
.enter().append('text')
.text(function(d) {
var series_data = getParentData(this);
return series_data.name + ': ' + d;
});
// Input: 1, Input: 2, Input: 3
mixins
mixins.Series
Mixin for handling series data
seriesKey
seriesKey(d)
-
d
(Any)Series object with
key
Get key for given series data
seriesValues
seriesValues(d)
-
d
(Any)Series object with
values
array
Get values for given series data
seriesClass
seriesClass(d, i)
-
d
(Any) -
i
(Number)
Get class for given series data
seriesIndex
seriesIndex(d, i)
-
d
(Any) -
i
(Number)
Get index for given data-point of series
seriesData
Get parent series data for given data-point
itemStyle
itemStyle(d, [i], [j])
-
d
(Any) -
[i]
(Number) -
[j]
(Number)
(di) Get style given series data or data-point
(Uses “style” object on d
, if defined)
seriesCount
Get series count for chart
seriesLayer
seriesLayer(name, selection, options)
-
name
(String) -
selection
(Selection) -
options
(Object)(
dataBind
andinsert
required)
Extension of layer() that handles data-binding and layering for series data.
- Updates
dataBind
method to access underlying series values - Creates group layer for each series in chart
- Should be used just like layer()
d3.chart('Chart').extend('Custom', helpers.mixin(mixins.Series, {
initialize: function() {
this.seriesLayer('Circles', this.base, {
// Create group for each series on this.base
// and calls the following for each series item
// (entire layer is called twice: series-1 and series-2)
dataBind: function(data) {
// 1. data = [1, 2, 3]
// 2. data = [4, 5, 6]
},
insert: function() {
// Same as chart.layer
// (where "this" is series group layer)
},
events: {
// Same as chart.layer
}
});
}
}));
// ...
chart.draw([
{key: 'series-1', values: [1, 2, 3]},
{key: 'series-2', values: [4, 5, 6]}
]);
mixins.XY
Mixin for handling XY data
xScale
{Object|d3.scale}
Get/set x-scale with d3.scale
or with object (uses helpers.createScale
)
xScale
{Object|d3.scale}
Get/set yscale with d3.scale
or with object (uses helpers.createScale
)
xKey
{String} ['x']
Key on data object for x-value
yKey
{String} ['y']
Key on data object for y-value
x
x(d, i)
-
d
(Any) -
i
(Number)
Get scaled x-value for given data-point
y
y(d, i)
-
d
(Any) -
i
(Number)
Get scaled y-value for given data-point
key
key(d, i)
-
d
(Any) -
i
(Number)
Get key for data-point. Looks for “key” on d
first, otherwise uses x-value.
x0
Get scaled x = 0
value
x0
Get scaled y = 0
value
xValue
xValue(d)
-
d
(Any)
Get x-value for data-point. Checks for xKey()
on d
first, otherwise uses d[0]
.
xValue({x: 10, y: 20}); // -> 10
xValue([10, 20]); // -> 10
yValue
yValue(d)
-
d
(Any)
Get y-value for data-point. Checks for yKey()
on d
first, otherwise uses d[1]
.
yValue({x: 10, y: 20}); // -> 20
yValue([10, 20]); // -> 20
setScales
Set x- and y-scale ranges (using setXScaleRange
and setYScaleRange
)
setXScaleRange
setXScaleRange(x_scale)
-
x_scale
(d3.scale)
Set range (0, width) for given x-scale
setYScaleRange
setYScaleRange(y_scale)
-
y_scale
(d3.scale)
Set range(height, 0) for given y-scale
getDefaultXScale
Get default x-scale: {data: this.data(), key: 'x'}
getDefaultYScale
Get default y-scale: {data: this.data(), key: 'y'}
mixins.XYValues
Mixin for charts of centered key,value data (x: index, y: value, key)
adjacentWidth
Determine width of data-point when displayed adjacent
layeredWidth
Determine layered width (width of group for adjacent)
itemWidth
Determine item width based on series display type (adjacent or layered)
getDefaultYScale
Override default x-scale to use ordinal type: {type: 'ordinal', data: this.data(), key: 'y', centered: true}
mixins.XYInverted
Mixin for inverting XY calculations with x vertical, increasing bottom-to-top and y horizontal, increasing left-to-right
x
x(d, i)
-
d
(Any) -
i
(Number)
Get x-value for plotting (scaled y-value)
y
y(d, i)
-
d
(Any) -
i
(Number)
Get y-value for plotting (scaled x-value)
x0
Get scaled y = 0 value (along x-axis)
x0
Get scaled x = 0 value (along y-axis)
setXScaleRange
setXScaleRange(x_scale)
-
x_scale
(d3.scale)
Set range (height, 0) for given x-scale
setYScaleRange
setYScaleRange(y_scale)
-
y_scale
(d3.scale)
Set range (0, width) for given y-scale
mixins.Labels
Mixin for handling labels in charts
attachLabels
Call during chart initialization to add labels to chart
d3.chart('Chart').extend('Custom', helpers.mixin(Labels, {
initialize: function() {
// this.layer()...
// Attach labels layer
this.attachLabels();
}
}));
labels
{Object}
Options passed to labels chart
d3.chart('Chart').extend('Custom', helpers.mixin(Labels, {
// ...
}));
// ...
chart.labels(true); // -> display labels with defaults
chart.labels(false); // -> hide labels
chart.labels({offset: 10}); // -> pass options to labels chart
d3.select('#chart')
.chart('Compose', function(data) {
return {
charts: {
custom: {labels: {offset: 10}}
}
};
});
mixins.LabelsXY
Mixin for handling labels in XY charts
(proxies x
and y
to properly place labels for XY charts)
Inherits
mixins.Hover
Mixin for handling common hover behavior that adds standard onMouseEnter
, onMouseMove
, and onMouseLeave
handlers
and getPoint
helper for adding helpful meta information to raw data point.
getPoint
getPoint(d, i, j)
-
d
(Any) -
i
(Number) -
j
(Number)
}
Get point information for given data-point
mouseEnterPoint
mouseEnterPoint(d, i, j)
-
d
(Any) -
i
(Number) -
j
(Number)
Call to trigger mouseenter:point when mouse enters data-point
d3.chart('Chart').extend('Bars', helpers.mixin(Hover, {
initialize: function() {
this.layer('bars', this.base, {
// dataBind...
insert: function() {
// Want to trigger enter/leave point
// when mouse enter/leaves bar (rect)
var chart = this.chart();
return this.append('rect')
.on('mouseenter', chart.mouseEnterPoint)
.on('mouseleave', chart.mouseLeavePoint);
}
// events...
})
}
}));
mouseleavePoint
mouseleavePoint(d, i, j)
-
d
(Any) -
i
(Number) -
j
(Number)
Call to trigger mouseleave:point when mouse leaves data-point
d3.chart('Chart').extend('Bars', helpers.mixin(Hover, {
initialize: function() {
this.layer('bars', this.base, {
// dataBind...
insert: function() {
// Want to trigger enter/leave point
// when mouse enter/leaves bar (rect)
var chart = this.chart();
return this.append('rect')
.on('mouseenter', chart.mouseEnterPoint)
.on('mouseleave', chart.mouseLeavePoint);
}
// events...
})
}
}));
onMouseEnter
onMouseEnter(position)
-
position
(Object)(chart and container {x,y} position of mouse)
-
chart
(Object){x, y} position relative to chart origin
-
container
(Object){x, y} position relative to container origin
-
(Override) Called when mouse enters container
onMouseMove
onMouseMove(position)
-
position
(Object)(chart and container {x,y} position of mouse)
-
chart
(Object){x, y} position relative to chart origin
-
container
(Object){x, y} position relative to container origin
-
(Override) Called when mouse moves within container
onMouseLeave
(Override) Called when mouse leaves container
mixins.HoverPoints
Mixin for automatically triggering “mouseenter:point”/”mouseleave:point” for chart data points that are within given hoverTolerance
.
hoverTolerance
{Number} [Infinity]
Hover tolerance (in px) for calculating close points
mixins.Transition
Mixin for handling common transition behaviors
delay
{Number|Function} [(use container value, if available)]
Delay start of transition by specified milliseconds.
duration
{Number|Function} [(use container value, if available)]
Transition duration in milliseconds.
ease
{String|Function} [(use container value, if available)]
Transition ease function
- See: Transitions#ease
- Note: arguments to pass to
d3.ease
are not supported
setupTransition
setupTransition(selection)
-
selection
(d3.selection)
Setup delay, duration, and ease for transition
d3.chart('Chart').extend('Custom', helpers.mixin(Transition, {
initialize: function() {
this.layer('circles', this.base, {
// ...
events: {
'merge:transition': function() {
// Set delay, duration, and ease from properties
this.chart().setupTransition(this);
}
}
});
}
}));
mixins.StandardLayer
Mixin to create standard layer to make extending charts straightforward.
d3.chart('Chart').extend('Custom', helpers.mixin(StandardLayer, {
initialize: function() {
this.standardLayer('main', this.base.append('g'))
// dataBind, insert, events are defined on prototype
},
onDataBind: function(selection, data) {
// ...
},
onInsert: function(selection) {
// ...
},
onEnter: function(selection) {
// ...
},
onUpdateTransition: function(selection) {
// ...
},
// all d3.chart events are available: onMerge, onExit, ...
}));
standardLayer
standardLayer(name, selection)
-
name
(String) -
selection
(d3.selection)
extension of layer()
that uses standard methods on prototype for extensibility.
d3.chart('Chart').extend('Custom', helpers.mixin(StandardLayer, {
initialize: function() {
this.standardLayer('circles', this.base.append('g'));
}
// onDataBind, onInsert, etc. work with "circles" layer
}));
standardSeriesLayer
standardSeriesLayer(name, selection)
-
name
(String) -
selection
(d3.selection)
extension of seriesLayer()
that uses standard methods on prototype for extensibility.
d3.chart('Chart').extend('Custom', helpers.mixin(StandardLayer, {
initialize: function() {
this.standardSeriesLayer('circles', this.base.append('g'));
},
// onDataBind, onInsert, etc. work with "circles" seriesLayer
}));
onDataBind
onDataBind(selection, data)
-
selection
(d3.selection) -
data
(Any)
Called for standard layer’s dataBind
onInsert
onInsert(selection)
-
selection
(d3.selection)
Called for standard layer’s insert
onEnter
onEnter(selection)
-
selection
(d3.selection)
Call for standard layer’s events['enter']
onEnterTransition
onEnterTransition(selection)
-
selection
(d3.selection)
Call for standard layer’s events['enter:transition']
onUpdate
onUpdate(selection)
-
selection
(d3.selection)
Call for standard layer’s events['update']
onUpdateTransition
onUpdateTransition(selection)
-
selection
(d3.selection)
Call for standard layer’s events['update']
onMerge
onMerge(selection)
-
selection
(d3.selection)
Call for standard layer’s events['merge']
onMergeTransition
onMergeTransition(selection)
-
selection
(d3.selection)
Call for standard layer’s events['merge:transition']
onExit
onExit(selection)
-
selection
(d3.selection)
Call for standard layer’s events['exit']
onExitTransition
onExitTransition(selection)
-
selection
(d3.selection)
Call for standard layer’s events['exit:transition']
Change Log
0.15.0
- Add Gridlines component
- Update and simplify
property
- Update label positioning
- Update class system to remove implicit initialize and transform cascade
- Remove deprecated xy extension and Object-style layout
- 0.15.1 Fix Overlay.position and labels with stacked bars bugs
- 0.15.2 Fix Gridlines onExit bug and slide down Bars on exit
- 0.15.3 Fix layout calculation and axis transition bugs
- 0.15.4 Fix component centering and mousemove in IE bugs
- 0.15.5 Fix
__proto__
and Overlay transform issues - 0.15.6 Fix Legend type not updating bug
- 0.15.7 Add banner to dist css and tweak label defaults
- 0.15.8 Fix margins issue for centered components
- 0.15.9 Fix delayed gridlines attachment bug and fix undefined config issue
- 0.15.10 Fix legend expanding on redraw issue
- 0.15.11 Update build (remove grunt), start new draw architecture, convert tests to mocha + jsdom
- 0.15.12 Include license/version in minified dist
- 0.15.13 Improve backwards compatibility with new architecture
0.14.0
- New layout system
- Remove dependency on Underscore
- Only set
charts
andcomponents
from options function - Convert
Compose.charts
andCompose.components
to arrays - Add helpers for charts and components
- Update mouse listening
- Update overlay positioning
- New module system
- 0.14.1 Fix undefined
XYInverted
bug - 0.14.2 Expose d3c global
- 0.14.3 Add
Text
andAxisTitle
components, update default margins, addcentered
component property - 0.14.4 Set transitions on
Compose
, updatestack
, and add hover listeners toLegend
- 0.14.5 Disable x0 and y0 position for axes until tested more thoroughly, refactor layout
- 0.14.6 Fix vertical Axis bug, absolute positioning Compose bug, unknown position bug, and property.previous bug
- 0.14.7 (Temporarily) Re-instantiate component on position change to avoid nasty side effects
0.13.0
- Add responsive support for
div
selections, withwidth
andheight
used forviewBox
- Add
StandardLayer
to improve extensibility in standard charts and components - Add
Transition
mixin for standard approach to addingduration
,delay
, andease
to charts/layers - 0.13.1 Add
Overlay
component and layer type - 0.13.2 Add
xKey
andyKey
tomixins.XY
and lots of docs updates - 0.13.3 For
xy
, add given components after generated - 0.13.4 Clear unset options on set and fix axis layout issue for inverted
- 0.13.5 Fix bars axis offset issue
- 0.13.6 Additional bar offset tweaks
0.12.0
- Add
centered
andadjacent
options forcreateScale
- Move x calculations for Values to
createScale
- Remove
LineValues
andAxisValues
to use scale instead - Rename
Line
toLines
- Bugfixes in
Legend
,Axis
, andMulti
- Refactor
Lines
chart - Add hover to labels
- Move hover points into separate
HoverPoints
mixin - Refactor mouse events
- 0.12.1 Complete rename to d3.compose
- 0.12.2 Move xy to d3.compose namespace
- 0.12.3 Fix version number
- 0.12.4 Fix data handling bug in Compose.draw, fix examples, and move
stack
back to helpers - 0.12.5 Handle
line-height: normal
inalignText
- 0.12.6 Split merge and layout in labels for better override
- 0.12.7 Fix cached config bug
- 0.12.8 Fix improperly updating series class/style bug
- 0.12.9 Fix Lines indexing bug and set class/style for charts on merge instead of insert
- 0.12.10 Allow 0/null for transition (delay, duration, ease) values
- 0.12.11 Use utils instead of underscore
- 0.12.12 Fix label collisions bug
- 0.12.13 Add
HorizontalBar
andHorizontalStackedBars
; makeBars
,Lines
, andTitle
more extensible; fixundefined
data bug; namespace charts - 0.12.14 Add horizontal bars swatch to legend
0.11.0
- New legend data format
- Fix creating legend from
xy
extension - New
registerSwatch
method (move method out of charts) - More options
- Split build into three parts: core, core + mixins, core + mixins + charts/components
- Simplify and cleanup
- 0.11.1 Labels extension bugfix and Bars offset bugfix
0.10.0
- Simplify charts and components
- Simplify draw/redraw
- Simplify attach/detach
- Simplify properties
- Merge
Container
andMulti
- Move axes, title, and legend out of Multi and into
xy
extension - Move z-index out of helpers
- Isolate underscore in utils