Page MenuHomePhabricator

Insert error functionality in MitkChart
Closed, DuplicatePublic

Description

Is currently available in QmitkPlotWidget. Add it to MitkChart.

  • SetCurveData(id, xValues, yValues, xLowerError, xUpperError, yLowerError, yUpperError)
  • SetErrorPen
  • SetErrorStylesSymbols

Event Timeline

Error Bars are currently not supported by C3.js (see https://github.com/c3js/c3/issues/253)

We would need to implement it into C3.js ourselves or find a different solution. @hentsch and I will discuss it.

One current use case of error bars is in the CEST view:

error_bars.png (664×1 px, 15 KB)

While this functionality only uses y error bars, it would be a good idea to support x error bars as well. Optionally supporting error corridors would be a nice to have, but is not something I would insist on.

Please note that error bars can be asymmetric.

following code implements error bars on bar charts:

var chart = c3.generate({
    data: {
        columns: [
            ['data1', 300, 350, 300, 200, 150, 230],
        ],
        types: {
            data1: 'bar',
        }
    },
        onresized: function () {
        updateErrorBars(); // need to be fixed..
    }     
    
});

var errors = [50, 20, 30, 20, 40, 100];

var errorBars = d3.select('#chart svg .c3-chart').append('g');

errorBars.selectAll('path')
  .data(errors)
    .enter().append('path')
    .attr('class', function (d, i) { return 'error-line-' + i; });

function updateErrorBars() {

    d3.selectAll('.c3-bar').each(function (d, i) {
    
        var segList = this.pathSegList,
            yPos = segList.getItem(1).y,
            xPos = (segList.getItem(2).x + segList.getItem(0).x) / 2;

        errorBars.select('.error-line-' + i)
            .attr('d', function (d) {
                return 'M' + xPos + ',' + (yPos + d/2) + ' ' +
                    'L' + xPos + ',' + (yPos - d/2) + ' ' +
                    'M' + (xPos - 5) + ',' + (yPos + d/2) + ' ' +
                    'L' + (xPos + 5) + ',' + (yPos + d/2) + ' ' +
                    'M' + (xPos - 5) + ',' + (yPos - d/2) + ' ' +
                    'L' + (xPos + 5) + ',' + (yPos - d/2) + ' ' +
                    'z';
            });

    });
};
setTimeout(updateErrorBars, 500);

With line chart, it's problematic because of deprecated svg function that is no longer available in Chromium (which is used also by QWebEngineView). See https://stackoverflow.com/questions/34352624/alternative-for-deprecated-svg-pathseglist

Following code implements error bars also with line (works also with current chrome *yeah*):

var chart = c3.generate({
    data: {
        columns: [
            ['data1', 300, 350, 300, 200, 150, 230],
        ],
        types: {
            data1: 'line',
        }
    },
        onresized: function () {
        updateErrorBars(); // need to be fixed..
    }     
    
});

var errors = [50, 20, 30, 20, 40, 100];

var errorBars = d3.select('#chart svg .c3-chart').append('g');

errorBars.selectAll('path')
  .data(errors)
    .enter().append('path')
    .attr('class', function (d, i) { return 'error-line-' + i; });

function updateErrorBars() {
    d3.selectAll('.c3-line').each(function (d, i) {
     var dataLength = chart.data.values('data1').length
	for (i = 0; i<dataLength; i++){
        var segList = this.pathSegList,
            yPos = segList.getItem(i).y,
            xPos = (segList.getItem(i).x + segList.getItem(i).x) / 2;
            errorBars.select('.error-line-' + i)
            .attr('d', function (d) {
                return 'M' + xPos + ',' + (yPos + d/2) + ' ' +
                    'L' + xPos + ',' + (yPos - d/2) + ' ' +
                    'M' + (xPos - 5) + ',' + (yPos + d/2) + ' ' +
                    'L' + (xPos + 5) + ',' + (yPos + d/2) + ' ' +
                    'M' + (xPos - 5) + ',' + (yPos - d/2) + ' ' +
                    'L' + (xPos + 5) + ',' + (yPos - d/2) + ' ' +
                    'z';
            });
         }
    });
};
setTimeout(updateErrorBars, 500);

This is an example for error bars in multiple datasets (line and bar at the same time).

Drawbacks:

  • This code gets more complicated every minute we work on it. It will be nearly impossible to understand and/or change it in the future.
  • The size of the errorbars is now in pixels. We would need to scale them according to the zoom-level of the chart. This would make it even more complicated.
  • When the chart is resized, the error bars are not updated and stay in the previous position.
//=========================================================================
function updateErrorBars(chartTypes){
   var lineKeyList = []
   var barKeyList = []
  for (var key in chartTypes)
  {
    if (chartTypes[key]=='line'){
    //alert("line" + key)
      lineKeyList.push(key)
    }
    else if (chartTypes[key]=='bar'){
    //alert("bar" + key)
      barKeyList.push(key)
    }
  }
  
  updateErrorBarsLine(lineKeyList)
  updateErrorBarsBar(barKeyList)
}

function updateErrorBarsLine(lineKeyList) {
		var loopCount = -1
    d3.selectAll('.c3-line').each(function (d, i) {
    loopCount++
    var segList = this.pathSegList
    var dataLength = segList.numberOfItems
    for (i = 0; i<dataLength; i++){
    	yPos = segList.getItem(i).y,
      xPos = (segList.getItem(i).x + segList.getItem(i).x) / 2;
      //alert('.error-line-data' + loopCount + '-' + i)
      errorBars.get(lineKeyList[loopCount]).select('.error-' + lineKeyList[loopCount] + '-' + i)
      .attr('d', function (d) {
      	return 'M' + xPos + ',' + (yPos + d/2) + ' ' +
        	'L' + xPos + ',' + (yPos - d/2) + ' ' +
          'M' + (xPos - 5) + ',' + (yPos + d/2) + ' ' +
          'L' + (xPos + 5) + ',' + (yPos + d/2) + ' ' +
          'M' + (xPos - 5) + ',' + (yPos - d/2) + ' ' +
          'L' + (xPos + 5) + ',' + (yPos - d/2) + ' ' +
          'z';
        });
      }
    });
};

function updateErrorBarsBar(barKeyList) {

    loopCount = 0;
    indx = -1
    len_data = dataLength.get(barKeyList[loopCount])
    d3.selectAll('.c3-bar').each(function (d, i) {
        indx++
        if (indx==len_data){
        	indx = 0
          loopCount++
          len_data = dataLength.get(barKeyList[loopCount])
        }
        var segList = this.pathSegList,
            yPos = segList.getItem(1).y,
            xPos = (segList.getItem(2).x + segList.getItem(0).x) / 2;
            
        
        errorBars.get(barKeyList[loopCount]).select('.error-' + barKeyList[loopCount] + '-' + indx)
            .attr('d', function (d) {
                return 'M' + xPos + ',' + (yPos + d/2) + ' ' +
                    'L' + xPos + ',' + (yPos - d/2) + ' ' +
                    'M' + (xPos - 5) + ',' + (yPos + d/2) + ' ' +
                    'L' + (xPos + 5) + ',' + (yPos + d/2) + ' ' +
                    'M' + (xPos - 5) + ',' + (yPos - d/2) + ' ' +
                    'L' + (xPos + 5) + ',' + (yPos - d/2) + ' ' +
                    'z';
            });

    });
    
 };

//=========================================================================
//main()
var chart = c3.generate({
    data: {
        columns: [
            ['data1', 300, 350, 300, 200, 150, 230],
            ['data2', 200, 280, 310, 130, 250],
            ['data3', 30, 30, 30, 30, 30, 30, 30, 30]
        ],
        types: {
            data1: 'line',
            data2: 'bar',
            data3: 'bar'
        }
    },
        onresized: function () {
        updateErrorBars(data); // need to be fixed..
    }     
    
});

var errors1 = [50, 20, 30, 20, 40, 100];
var errors2 = [10, 10, 10, 10, 10];
var errors3 = [30, 30, 30, 30, 30, 30, 30, 30];

var errors = new Map()

errors.set('data1', errors1)
errors.set('data2', errors2)
errors.set('data3', errors3)

var chartTypes = {'data1' : 'line', 'data2' : 'bar', 'data3' : 'bar'}
dataLength=new Map();
for(var key in chartTypes){
  dataLength.set(key, errors.get(key).length);
}


var errorBars = new Map()
for(var key in chartTypes)
{
	errorBars.set(key, d3.select('#chart svg .c3-chart').append('g'));
  errorBars.get(key).selectAll('path')
  .data(errors.get(key))
    .enter().append('path')
    .attr('class', function (d, i) { return 'error-' + key + '-' + i; });
}

setTimeout(updateErrorBars(chartTypes), 500);

Conclusion: It's not worth the effort.
MitkChart will only get error bars if the feature is implemented in C3js.

hentsch claimed this task.
hentsch added a subscriber: steint.
hentsch closed this task as a duplicate of Restricted Maniphest Task.Jan 18 2019, 1:22 PM