Reputation: 4062
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.
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
Reputation: 7128
Given this chart:
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:
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):
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