How do you add error bars to line-graphs in nvd3.js graphs
Asked Answered
H

1

6

I have recently come across the nv.d3 js graph tools that look fantastic. I've got everything working; however.....I am thinking about implementing them in combination with a html5 slide-show for presenting scientific data.

For this reason it would be fantastic to add error bars to the values in line- and bar graphs. It would simply be assigning a single value to each data-point that would define the height of the error bar.

I'm guessing this may be difficult, but it would be a great feature to add to the scripts.

Any suggestions? I'm not a coder unfortunately

Hypotension answered 6/11, 2013 at 20:22 Comment(1)
This is not currently implemented -- unless you want to implement it yourself, you would have to open a feature request.Tomokotomorrow
H
0

So I created an error bar function for this, the value of isError in the array gives you the size of the error bar at each point:

      <div id="chart2" style="height: 700px; width: 900px; margin-left:auto; margin-right:auto;">
      <svg id="chart2" style="height: 700px; width: 900px;"></svg>
    </div>
    <script src="nv.d3.js"></script>
      <script>

    var charts;

    nv.addGraph(Bodyweight(this));

    //nv.addGraph(function() {
    function Bodyweight(c) {
      charts = nv.models.lineChart()
      .forceY([0])
      .options({
      margin: {left: 100, bottom: 100},
      x: function(d,i) { return i},
      showXAxis: true,
      showYAxis: true,
      transitionDuration: 250
      })
      ;
      charts.useInteractiveGuideline(true);

      var labelValues2 = ['20', '22', '24', '26', '28', '30', '32', '34', '36', '38', '40', '42', '44', '46', '48', '50'];
      charts.xAxis
      .axisLabel("Time (Weeks)")
      //.axisLabelDistance(120)
      .tickFormat(function(d){
      return labelValues2[d];
      });

      charts.yAxis
      .axisLabel('Bodyweight (g)')
      .tickFormat(d3.format(',.1f'))
      //.scale().domain([0, maxValue])
      ;

      d3.select('#chart2 svg')
      .datum(sinAndCos2())
      .call(charts);

      nv.utils.windowResize(function() { d3.select('#chart2 svg').call(charts) });

      charts.dispatch.on('stateChange', function(e) { nv.log('New State:', JSON.stringify(e)); });

      return charts;
    };

    function sinAndCos2() {
      var wt_sd = [{x: 1, y: 29.7, isError: 0.8},{x: 2, y: 31.0, isError: 0.9},{x: 3, y: 30.7, isError: 1.0},{x: 4, y: 31.2, isError: 1.0},{x: 5, y: 32.3, isError: 1.2},{x: 6, y: 32.4, isError: 1.2},{x: 7, y: 33.3, isError: 1.3},{x: 8, y: 33.9, isError: 1.3},{x: 9, y: 35.3, isError: 1.3},{x: 10, y: 36.7, isError: 1.4},{x: 11, y: 36.2, isError: 1.4},{x: 12, y: 37.1, isError: 1.5},{x: 13, y: 38.3, isError: 1.6},{x: 14, y: 39.3, isError: 1.5},{x: 15, y: 40.0, isError: 1.6},{x: 16, y: 40.9, isError: 1.6}],
         wt_hfd = [{x: 1, y: 29.7, isError: 0.6},{x: 2, y: 32.5, isError: 0.8},{x: 3, y: 34.8, isError: 0.9},{x: 4, y: 36.8, isError: 1.0},{x: 5, y: 38.7, isError: 1.1},{x: 6, y: 40.9, isError: 1.3},{x: 7, y: 43.0, isError: 1.5},{x: 8, y: 44.6, isError: 1.8},{x: 9, y: 46.2, isError: 1.7},{x: 10, y: 49.0, isError: 1.5},{x: 11, y: 50.3, isError: 1.5},{x: 12, y: 52.1, isError: 1.3},{x: 13, y: 52.2, isError: 1.3},{x: 14, y: 53.5, isError: 1.2},{x: 15, y: 53.8, isError: 1.2},{x: 16, y: 53.1, isError: 1.5}],
          wt_cr = [{x: 1, y: 28.2, isError: 0.4},{x: 2, y: 25.5, isError: 0.3},{x: 3, y: 23.1, isError: 0.2},{x: 4, y: 23.2, isError: 0.2},{x: 5, y: 23.4, isError: 0.3},{x: 6, y: 22.5, isError: 0.3},{x: 7, y: 22.5, isError: 0.3},{x: 8, y: 23.0, isError: 0.3},{x: 9, y: 22.1, isError: 0.3},{x: 10, y: 22.4, isError: 0.3},{x: 11, y: 22.5, isError: 0.2},{x: 12, y: 23.1, isError: 0.3},{x: 13, y: 23.5, isError: 0.2},{x: 14, y: 23.6, isError: 0.3},{x: 15, y: 23.7, isError: 0.3},{x: 16, y: 23.8, isError: 0.2}],
        wt_resv = [{x: 1, y: 27.6, isError: 0.3},{x: 2, y: 28.0, isError: 0.4},{x: 3, y: 27.8, isError: 0.4},{x: 4, y: 28.6, isError: 0.5},{x: 5, y: 29.2, isError: 0.5},{x: 6, y: 29.3, isError: 0.6},{x: 7, y: 29.8, isError: 0.6},{x: 8, y: 30.3, isError: 0.6},{x: 9, y: 31.3, isError: 0.8},{x: 10, y: 32.5, isError: 0.9},{x: 11, y: 32.5, isError: 1.0},{x: 12, y: 33.7, isError: 1.1},{x: 13, y: 34.5, isError: 1.1},{x: 14, y: 34.8, isError: 1.1},{x: 15, y: 34.9, isError: 1.2},{x: 16, y: 34.8, isError: 1.2}],
         csb_sd = [{x: 1, y: 27.1, isError: 0.8},{x: 2, y: 27.1, isError: 0.6},{x: 3, y: 26.9, isError: 0.6},{x: 4, y: 27.7, isError: 0.8},{x: 5, y: 29.0, isError: 1.2},{x: 6, y: 29.1, isError: 1.0},{x: 7, y: 29.4, isError: 1.1},{x: 8, y: 30.0, isError: 1.2},{x: 9, y: 30.8, isError: 1.3},{x: 10, y: 31.7, isError: 1.5},{x: 11, y: 32.2, isError: 1.6},{x: 12, y: 31.9, isError: 1.4},{x: 13, y: 32.1, isError: 1.3},{x: 14, y: 31.9, isError: 1.3},{x: 15, y: 31.8, isError: 1.5},{x: 16, y: 31.8, isError: 1.5}],
        csb_hfd = [{x: 1, y: 28.1, isError: 0.9},{x: 2, y: 29.4, isError: 1.1},{x: 3, y: 31.6, isError: 1.3},{x: 4, y: 34.1, isError: 1.6},{x: 5, y: 34.2, isError: 1.4},{x: 6, y: 36.3, isError: 1.5},{x: 7, y: 37.7, isError: 1.6},{x: 8, y: 40.1, isError: 1.6},{x: 9, y: 41.0, isError: 1.6},{x: 10, y: 42.8, isError: 1.6},{x: 11, y: 44.1, isError: 1.5},{x: 12, y: 44.2, isError: 1.6},{x: 13, y: 44.9, isError: 1.6},{x: 14, y: 44.8, isError: 1.6},{x: 15, y: 43.8, isError: 1.6},{x: 16, y: 44.0, isError: 1.7}],
         csb_cr = [{x: 1, y: 26.1, isError: 0.8},{x: 2, y: 22.3, isError: 0.6},{x: 3, y: 20.3, isError: 0.3},{x: 4, y: 20.3, isError: 0.3},{x: 5, y: 19.8, isError: 0.3},{x: 6, y: 20.3, isError: 0.4},{x: 7, y: 19.8, isError: 0.3},{x: 8, y: 20.2, isError: 0.2},{x: 9, y: 20.3, isError: 0.2},{x: 10, y: 19.8, isError: 0.3},{x: 11, y: 20.1, isError: 0.3},{x: 12, y: 19.9, isError: 0.2},{x: 13, y: 19.7, isError: 0.4},{x: 14, y: 20.0, isError: 0.3},{x: 15, y: 20.0, isError: 0.3},{x: 16, y: 19.2, isError: 0.3}],
       csb_resv = [{x: 1, y: 25.4, isError: 0.4},{x: 2, y: 26.2, isError: 1.0},{x: 3, y: 25.3, isError: 0.5},{x: 4, y: 25.8, isError: 0.6},{x: 5, y: 26.6, isError: 0.7},{x: 6, y: 27.5, isError: 0.8},{x: 7, y: 28.3, isError: 0.9},{x: 8, y: 29.3, isError: 1.1},{x: 9, y: 29.6, isError: 1.1},{x: 10, y: 30.6, isError: 1.3},{x: 11, y: 31.2, isError: 1.3},{x: 12, y: 31.9, isError: 1.2},{x: 13, y: 31.5, isError: 1.2},{x: 14, y: 31.7, isError: 1.2},{x: 15, y: 31.3, isError: 1.1},{x: 16, y: 31.4, isError: 1.2}]
      ;
      return [
      {
        //area: true,
        values: wt_sd,
        key: "WT SD",
        color: "#FF0000",
        isError: true,
        isFull: true
      },
      {
        values: wt_hfd,
        key: "WT HFD",
        color: "#0066FF",
        isError: true,
        isFull: true
      },
      {
        values: wt_cr,
        key: "WT CR",
        color: "#33CC33",
        isError: true,
        isFull: true
      },
      {
        values: wt_resv,
        key: "WT Resv",
        color: "#CC00FF",
        isError: true,
        isFull: true
      },
      {
        //area: true,
        values: csb_sd,
        key: "Csb SD",
        color: "#FF0000",
        isDashed: true,
        isError: true,
        isFull: false
      },
      {
        values: csb_hfd,
        key: "Csb HFD",
        color: "#0066FF",
        isDashed: true,
        isError: true,
        isFull: false
      },
      {
        values: csb_cr,
        key: "Csb CR",
        color: "#33CC33",
        isDashed: true,
        isError: true,
        isFull: false
      },
      {
        values: csb_resv,
        key: "Csb Resv",
        color: "#CC00FF",
        isDashed: true,
        isError: true,
        isFull: false
      }
      ];
    }
    </script>

And here is the updated chart() function from nv.d3.js that you can copy paste on top of the old chart(). It also adds the option of using dashed lines if you have multiple groups that needs to be better separated;

function chart(selection) {
    selection.each(function(data) {
      var availableWidth = width - margin.left - margin.right,
          availableHeight = height - margin.top - margin.bottom,
          container = d3.select(this);

      //------------------------------------------------------------
      // Setup Scales

      x = scatter.xScale();
      y = scatter.yScale();

      x0 = x0 || x;
      y0 = y0 || y;

      //------------------------------------------------------------
      //alert(y0);

      //------------------------------------------------------------
      // Setup containers and skeleton of chart

      var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
      var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
      var defsEnter = wrapEnter.append('defs');
      var gEnter = wrapEnter.append('g');
      var g = wrap.select('g')

      gEnter.append('g').attr('class', 'nv-groups');
      gEnter.append('g').attr('class', 'nv-scatterWrap');

      wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

      //------------------------------------------------------------




      scatter
        .width(availableWidth)
        .height(availableHeight)

       //alert(scatter.height(availableHeight));

      var scatterWrap = wrap.select('.nv-scatterWrap');
          //.datum(data); // Data automatically trickles down from the wrap

      scatterWrap.transition().call(scatter);



      defsEnter.append('clipPath')
          .attr('id', 'nv-edge-clip-' + scatter.id())
        .append('rect');

      wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
          .attr('width', availableWidth)
          .attr('height', availableHeight);

      g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
      scatterWrap
          .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');




      var groups = wrap.select('.nv-groups').selectAll('.nv-group')
          .data(function(d) { return d }, function(d) { return d.key });
      groups.enter().append('g')
          .style('stroke-opacity', 1e-6)
          .style('fill-opacity', 1e-6);
      groups.exit()
          .transition()
          .style('stroke-opacity', 1e-6)
          .style('fill-opacity', 1e-6)
          .remove();
      groups
          .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
          .classed('hover', function(d) { return d.hover })
          .style('fill', function(d,i){ return color(d, i) })
          .style('stroke', function(d,i){ return color(d, i)});
      groups
          .transition()
          .style('stroke-opacity', 1)
          .style('fill-opacity', .5);



      var areaPaths = groups.selectAll('path.nv-area').data(function(d) { return d.isArea ? [d] : [] }); // this is done differently than lines because I need to check if series is an area

        areaPaths.enter().append('path')
          .attr('class', 'nv-area')
          .attr('d', function(d) {
            return d3.svg.area()
                .interpolate(interpolate)
                .defined(defined)
                .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
                .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
                .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
                //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
                .apply(this, [d.values])
          });
      groups.exit().selectAll('path.nv-area')
           .remove();

      areaPaths
          .transition()
          .attr('d', function(d) {
            return d3.svg.area()
                .interpolate(interpolate)
                .defined(defined)
                .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
                .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
                .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
                //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
                .apply(this, [d.values])
          });


      var YcorMax=y.domain()[1];

      var errorPaths = groups.selectAll('path.nv-error').data(function(d) { return d.isError ? [d] : [] }); 
        errorPaths.enter().append('path')
          .attr('class', 'nv-error')
          .attr('d', function(d) {
            return d3.svg.area()
                .interpolate(interpolate)
                .defined(defined)
                .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
            .y0(function(d,i) {
                       var ez=Getez(d,i);
                var z=nv.utils.NaNtoZero(y(getY(d,i)));
                return z-((ez/YcorMax)*height);})           
               .y1(function(d,i) {
                       var ez=Getez(d,i);
                var z=nv.utils.NaNtoZero(y(getY(d,i)));  
                return z+((ez/YcorMax)*height);})
                .apply(this, [d.values])
          }).style("stroke-width", ("1px")).style('fill-opacity', .2).style('stroke-opacity', .5);

      groups.exit().selectAll('path.nv-error')
           .remove();

      errorPaths
          .transition()
          .attr('d', function(d) {
            return d3.svg.area()
                .interpolate(interpolate)
                .defined(defined)
                .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
            .y0(function(d,i) {
                       var ez=Getez(d,i);
                var z=nv.utils.NaNtoZero(y(getY(d,i)));
                return z-((ez/YcorMax)*height);})   
            .y1(function(d,i) {
                      var ez=Getez(d,i);
                var z=nv.utils.NaNtoZero(y(getY(d,i)));
                return z+((ez/YcorMax)*height);})
                .apply(this, [d.values])
          });        


        /////

      var linePaths = groups.selectAll('path.nv-line')
         // .data(function(d) { return [d.values] });
        .data(function (d) { return d.isDashed ? [] : [d.values] });

      linePaths.enter().append('path')
          .attr('class', 'nv-line')
          .attr('d',
            d3.svg.line()
              .interpolate(interpolate)
              .defined(defined)
              .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
              .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
          ).style("stroke-width", ("4px"));
              // );
      groups.exit().selectAll('path.nv-line')
          .transition()
          .attr('d',
            d3.svg.line()
              .interpolate(interpolate)
              .defined(defined)
              .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
              .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
          );
      linePaths
          .transition()
          .attr('d',
            d3.svg.line()
              .interpolate(interpolate)
              .defined(defined)
              .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
              .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
          );
var dashedLinePaths = groups.selectAll('path.nv-line')
          .data(function (d) { return d.isDashed ? [d.values] : [] });

      dashedLinePaths.enter().append('path')
          .attr('class', 'nv-line')
          .attr('d',
            d3.svg.line()
              .interpolate(interpolate)
              .defined(defined)
              .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
              .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
          ).style("stroke-dasharray", ("10, 10")).style("stroke-width", ("4px"));
 //).style("stroke-dasharray", ("10, 10"));
      dashedLinePaths
          .transition()
          .attr('d',
            d3.svg.line()
              .interpolate(interpolate)
              .defined(defined)
              .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
              .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
          );


      //store old scales for use in transitions on update
      x0 = x.copy();
      y0 = y.copy();

    });

    return chart;
  }
Hypotension answered 19/6, 2015 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.