Pio
Pio

Reputation: 4062

Add noise to chart (Google charts api)

I have to display around 20 lines within a line chart with Google's Line Chart. It might happen, that these lines overlap. What is the best way to add noise to the data so all the lines are visible.

enter image description here the values for cat1, cat2 and cat3 are the same but I want that the it is evident from the image, that they are really close - so my idea is that the lines should not overlap but be a bit apart. The user cannot assume, that all the values overlap since for some events, let's say D, the values might be missing.

Upvotes: 1

Views: 861

Answers (1)

jmac
jmac

Reputation: 7128

Given this chart:

Overlapping Lines

function drawVisualization() {
  // Create and populate the data table.
  var data = google.visualization.arrayToDataTable([
    ['x', '#1', '#2', '#3'],
    ['A', 1, 1, 1],
    ['B', 2, 2, 2],
    ['C', 3, 3, 3],
    ['D', 4, 4, 4],
    ['E', 5, 5, 5],
    ['F', 6, 6, 6],
    ['G', 7, 7, 7],
    ['H', 8, 8, 8],
    ['I', 9, 9, 9],
  ]);

  // Create and draw the visualization.
  new google.visualization.LineChart(document.getElementById('visualization')).
      draw(data, {width: 500, height: 400,
                  vAxis: {maxValue: 10}}
          );
}

One way is just to add a consistent +/- per series:

Fixed Dither

function drawVisualization() {
  // Create and populate the data table.
  var data = google.visualization.arrayToDataTable([
    ['x', '#1', '#2', '#3'],
    ['A', 1, 1, 1],
    ['B', 2, 2, 2],
    ['C', 3, 3, 3],
    ['D', 4, 4, 4],
    ['E', 5, 5, 5],
    ['F', 6, 6, 6],
    ['G', 7, 7, 7],
    ['H', 8, 8, 8],
    ['I', 9, 9, 9],
  ]);

  for (var i = 1;i < data.getNumberOfColumns();i++) {
    // Algorithm to add +/- 0.1 for each series
    var dither = Math.round((i - 1)/2)/5;
    if ( (i - 1) % 2 == 0 ) {
      dither = dither * -1;
    }
    for (var j = 0;j < data.getNumberOfRows();j++){
      // Add dither to series to display differently, but keep same data for tooltip
      data.setCell(j, i, data.getValue(j, i) + dither, data.getValue(j, i) + '', undefined)
    }
  }

  // Create and draw the visualization.
  new google.visualization.LineChart(document.getElementById('visualization')).
    draw(data, {width: 500, height: 400,
                vAxis: {maxValue: 10}}
        );
}

The issue with this method is that if the values of your axes or data change significantly, you won't be able to see the gap (because the resolution of the screen won't be big enough). To get around this issue, we would need to manually set the min/max values of the axes in order to be able to come up with an appropriate factor. For instance, from this answer we can take the following algorithm to determine min and max axes values that approximate what google will set for us automagically:

// Take the Max/Min of all data values in all graphs
var totalMax = 345;
var totalMin = -123;

// Figure out the largest number (positive or negative)
var biggestNumber = Math.max(Math.abs(totalMax),Math.abs(totalMin));

// Round to an exponent of 10 appropriate for the biggest number
var roundingExp = Math.floor(Math.log(biggestNumber) / Math.LN10);
var roundingDec = Math.pow(10,roundingExp);

// Round your max and min to the nearest exponent of 10
var newMax = Math.ceil(totalMax/roundingDec)*roundingDec;
var newMin = Math.floor(totalMin/roundingDec)*roundingDec;

// Determine the range of your values
var range = newMax - newMin;

// Define the number of gridlines (default 5)
var gridlines = 5;

// Determine an appropriate gap between gridlines
var interval = range / (gridlines - 1);

// Round that interval up to the exponent of 10
var newInterval = Math.ceil(interval/roundingDec)*roundingDec;

// Re-round your max and min to the new interval
var finalMax = Math.ceil(totalMax/newInterval)*newInterval;
var finalMin = Math.floor(totalMin/newInterval)*newInterval;

We can add this all together, and see that it will even work if all the values are increased in factor by 10 (which wouldn't have worked with the hard-coded version):

Dynamic Dither

function drawVisualization() {
  // Create and populate the data table.
  var data = google.visualization.arrayToDataTable([
    ['x', '#1', '#2', '#3'],
    ['A', 10, 10, 10],
    ['B', 20, 20, 20],
    ['C', 30, 30, 30],
    ['D', 40, 40, 40],
    ['E', 50, 50, 50],
    ['F', 60, 60, 60],
    ['G', 70, 70, 70],
    ['H', 80, 80, 80],
    ['I', 90, 90, 90],
  ]);

  // Get max and min values for the data table

  var totalMin = data.getValue(0,1);
  var totalMax = data.getValue(0,1);

  for (var i = 1;i < data.getNumberOfColumns();i++) {
    for (var j = 0;j < data.getNumberOfRows();j++){
      if ( data.getValue(j, i) < totalMin ) {
        totalMin = data.getValue(j, i);
      }          
      if ( data.getValue(j, i) > totalMax ) {
        totalMax = data.getValue(j, i);
      }
    }
  }

  // Calculate grid line axes and min/max settings

  // Figure out the largest number (positive or negative)
  var biggestNumber = Math.max(Math.abs(totalMax),Math.abs(totalMin));

  // Round to an exponent of 10 appropriate for the biggest number
  var roundingExp = Math.floor(Math.log(biggestNumber) / Math.LN10);
  var roundingDec = Math.pow(10,roundingExp);

  // Round your max and min to the nearest exponent of 10
  var newMax = Math.ceil(totalMax/roundingDec)*roundingDec;
  var newMin = Math.floor(totalMin/roundingDec)*roundingDec;

  // Determine the range of your values
  var range = newMax - newMin;

  // Define the number of gridlines (default 5)
  var gridlines = 5;

  // Determine an appropriate gap between gridlines
  var interval = range / (gridlines - 1);

  // Round that interval up to the exponent of 10
  var newInterval = Math.ceil(interval/roundingDec)*roundingDec;

  // Re-round your max and min to the new interval
  var finalMax = Math.ceil(totalMax/newInterval)*newInterval;
  var finalMin = Math.floor(totalMin/newInterval)*newInterval;

  // Calculate Dither

  for (var i = 1;i < data.getNumberOfColumns();i++) {
    // Algorithm to add +/- 0.1 for each series
    var dither = Math.round((i - 1)/2)/(10/newInterval);
    if ( (i - 1) % 2 == 0 ) {
      dither = dither * -1;
    }
    for (var j = 0;j < data.getNumberOfRows();j++){
      // Add dither to series to display differently, but keep same data for tooltip
      data.setCell(j, i, data.getValue(j, i) + dither, data.getValue(j, i) + '', undefined)
        }
        }

        // Create and draw the visualization.
        new google.visualization.LineChart(document.getElementById('visualization')).
        draw(data, {width: 500, height: 400,
        vAxis: {minValue: finalMin, maxValue: finalMax}}
        );
    }

Upvotes: 5

Related Questions