4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

How to add error bar to bar chart in c3js

Last updated at Posted at 2014-12-28

Screen Shot 2014-12-28 at 14.59.55.png

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]
          }
        },
      });

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?