How to add error bar to bar chart in c3js


![Screen Shot 2014-12-28 at 14.59.55.png](https://qiita-image-store.s3.amazonaws.com/0/26122/22b8c660-0df8-ed18-b349-1eed8a3157e5.png "Screen Shot 2014-12-28 at 14.59.55.png")

Following coffeescript code extends [c3.chart.internal.fn](https://github.com/masayuki0812/c3/releases/tag/0.3.0) 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]
          }
        },
      });

```