Following coffeescript code extends c3.chart.internal.fn and adds error bar to bar chart. I have only checked the code works with this specific type of bar chart though.
c3 = require 'c3'
_ = require 'lodash'
KLASS = {
chartErrorBars: 'chart-error-bars'
chartErrorBar: 'chart-error-bar'
errorLine: 'error-bar-line'
}
errorBarY = ($$, d, sign) ->
error = $$.config.data_errors?[d.id]?[d.index] || 0
$$.getYScale(d.id)(d.value + sign * error)
errorBarPoints = (d, index) ->
$$ = this
barIndices = $$.getShapeIndices($$.isBarType)
getPoints = $$.generateGetBarPoints(barIndices)
points = getPoints(d, d.index)
left = points[0][0]
right = points[2][0]
top = points[1][1]
bottom = points[0][1]
width = right - left
height = bottom - top
center = (left + right) / 2
upperErrorBarY = errorBarY($$, d, -1)
lowerErrorBarY = errorBarY($$, d, 1)
yScale = $$.getYScale
y0 = yScale.call($$, d.id)(0)
[upperErrorBarY, lowerErrorBarY, center - width / 5, center + width / 5, center][index]
errorBarLinePoints =
top: [2, 3, 1, 1]
vertical: [4, 4, 0, 1]
bottom: [2, 3, 0, 0]
errorBarLinePointX1 = (d) -> errorBarPoints.call(this, d.data, errorBarLinePoints[d.location][0])
errorBarLinePointX2 = (d) -> errorBarPoints.call(this, d.data, errorBarLinePoints[d.location][1])
errorBarLinePointY1 = (d) -> errorBarPoints.call(this, d.data, errorBarLinePoints[d.location][2])
errorBarLinePointY2 = (d) -> errorBarPoints.call(this, d.data, errorBarLinePoints[d.location][3])
c3.chart.internal.fn.redrawBar = _.wrap c3.chart.internal.fn.redrawBar, (f, durationForExit) ->
f.apply(this, [durationForExit])
$$ = this
barData = $$.barData.bind($$)
barIndices = $$.getShapeIndices($$.isBarType)
getPoints = $$.generateGetBarPoints(barIndices)
errorBar = $$.main.selectAll('.' + c3.chart.internal.fn.CLASS.bars).selectAll('.' + KLASS.chartErrorBar)
.data( (d) ->
if $$.config.data_errors[d.id]?.length > 0
barData(d)
else
[]
)
errorBar.enter().append('g')
.attr('class', (d) ->
[KLASS.chartErrorBar, "#{KLASS.chartErrorBar}-#{d.id}-#{d.index}"].join ' '
)
errorBar.exit().transition().duration(durationForExit)
.style('opacity', 0)
.remove()
lines = errorBar.selectAll('.' + KLASS.errorLine )
.data(
(d) ->
Object.keys(errorBarLinePoints).map (location) ->
data: d
location: location
)
lines.enter().append('line')
.attr('class', (d) ->
[
KLASS.errorLine
"#{KLASS.errorLine}-#{d.location}-#{d.data.id}-#{d.data.index}"
].join ' '
)
.attr('x1', _.bind errorBarLinePointX1, $$)
.attr('x2', _.bind errorBarLinePointX2, $$)
.attr('y1', _.bind errorBarLinePointY1, $$)
.attr('y2', _.bind errorBarLinePointY2, $$)
lines.exit().transition().duration(durationForExit)
.style('opacity', 0)
.remove()
c3.chart.internal.fn.addTransitionForBar = _.wrap c3.chart.internal.fn.addTransitionForBar, (f, transitions, drawBar) ->
$$ = this
transitions.push(
$$.main.selectAll('.' + c3.chart.internal.fn.CLASS.bars).selectAll('.' + KLASS.errorLine)
.attr('x1', _.bind errorBarLinePointX1, $$)
.attr('x2', _.bind errorBarLinePointX2, $$)
.attr('y1', _.bind errorBarLinePointY1, $$)
.attr('y2', _.bind errorBarLinePointY2, $$)
.style("fill", $$.color)
.style("opacity", 1)
)
f.call(this, transitions, drawBar)
c3.chart.internal.fn.getDefaultConfig = _.wrap c3.chart.internal.fn.getDefaultConfig, (f, args...) ->
_.merge f.apply(this, args), {
data_errors: {}
}
c3.chart.internal.fn.tooltipPosition = (data, width, height, element) ->
$$ = this
if element instanceof SVGRectElement # bar with error
y = errorBarY($$, data[0], 1)
center = errorBarPoints.call($$, 4, data[0])
tooltipLeft = $$.getCurrentPaddingLeft()
x = Math.round(center + tooltipLeft - width / 2)
y -= height + 10
else
unless element instanceof SVGCircleElement
circles = this.getCircles(data[0].index)
element = circles[0][0]
chartOffsetX = document.querySelector($$.config.bindto).getBoundingClientRect().left
graphOffsetX = document.querySelector("#{$$.config.bindto} g.c3-axis-y").getBoundingClientRect().right
tooltipWidth = document.getElementById('tooltip').parentNode.clientWidth
x = parseInt(element.getAttribute('cx')) + graphOffsetX - chartOffsetX - Math.floor(tooltipWidth/2)
y = element.getAttribute('cy')
y = y - height - 12
{top: y, left: x}
c3.chart.internal.fn.getYDomainMax = _.wrap c3.chart.internal.fn.getYDomainMax, (f, targets) ->
$$ = this
if ($$.config.data_groups.length > 0)
console.warn "this extension breaks data.groups."
ys = $$.getValuesAsIdKeyed(targets)
for k, data of ys
continue unless k of $$.config.data_errors
for v, i in data
if $$.config.data_errors[k][i]
data[i] += $$.config.data_errors[k][i]
$$.d3.max(Object.keys(ys).map (key) -> $$.d3.max(ys[key]) )
var chart = c3.generate({
bindto: '#chart',
data: {
x : 'variations',
columns: [
['variations'].concat( [0, 1, 2, 3, 4, 5].map(function (x) { return 'group ' + x}) ) ,
['data1', 43, 20, 40, 40, 15, 25],
['data2', 13, 110, 81, 43, 85, 55],
],
types: {
'data1': 'bar',
'data2': 'bar',
'error-data1': 'error',
},
errors: {
'data1': [2, 10, 14, 4, 5, 8],
'data2': [1, 20, 22, 18, 9, 18]
}
},
});