Gaps in Data on Google Charts Continuous Axis - javascript

I have a Google Chart with a continuous date-time X axis. My data comes in short bursts, with long delays between the bursts. I'd like to make the Chart have a non-continuous X axis, but still have the auto-generated timestamps during the samples. Is that possible?
Basically, say I have 3 samples, each which have 300 datapoints, recorded across 10 second intervals, but with hour gaps between them. I'd like to have my chart show the 30 seconds of data at a zoom level where it can be distinguished. Am I stuck?
Edit: Per #jmac's suggestion, here is an example of what the data looks like:
1360096658270, 10.228335
1360096658274, 10.308437
1360096658277, 10.294770
[...]
1360096673968, 9.014943
1360096673969, 8.971434
1360096673970, 9.041739
1360096673971, 9.097484
^^-- 15 seconds
<--- (~10 days)
1360989176509, 9.856928
1360989176513, 9.852907
1360989176517, 9.861740
1360989176523, 9.820416
1360989176527, 9.871401

Method 1: Multiple Charts
This is probably the simplest in concept (though still a hassle).
Summary:
Split data in to groups (eliminate the gaps)
Create a separate chart for each group
Eliminate the vAxis labels for every chart past the first
Create a consistent vAxis min/max value
Use CSS to line the charts up side to side
Details:
If you have a static data set, you can just split it by hand. If it isn't static, then you have to write some javascript to split up your data. I can't really help you here since I don't know how your data works.
As far as setting up the charts, I'll leave that up to you. I don't know how you want them formatted, so again I can't really help you with the current info.
To create a consistent axis value for all charts, you need to use some basic math in a javascript function to assign the same numbers to each vAxis max/min value. Here is a sample:
// 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;
Method 2: Multiple Series
As long as the people viewing your data understand they are different sets, then there's no reason the axis needs to say the exact date/time as long as they can easily figure that out elsewhere.
Summary:
Separate your data in to different series for each 'sequence'
Artificially shorten the gaps between sequences (if they are 15 seconds each, then have a 5 second gap between series, or just start every 15 seconds)
Format each different series with a name labeling when the run started/ended
Details:
Again, you will have to split your data manually or create javascript to do it, but what you want to do is to move each set of numbers in to its own column, like so:
1360096658270, 10.228335, null
1360096658274, 10.308437, null
1360096658277, 10.294770, null
[...]
1360096673968, 9.014943, null
1360096673969, 8.971434, null
1360096673970, 9.041739, null
1360096673971, 9.097484, null
^^-- 15 seconds
<--- (~10 days)
1360989176509, null, 9.856928
1360989176513, null, 9.852907
1360989176517, null, 9.861740
1360989176523, null, 9.820416
1360989176527, null, 9.871401
This will make each series be a different color (and have a different label in the legend/on mouseover), so you can see the difference between runs, but also get a nice tooltip saying "This data was gathered from X to Y" so that if the time the data was taken is important, it's still in there (albeit not on the X axis).
These are the easiest ways.
Method 3: Manually Editing the X-Axis Labels
The third way is the most flexible but also takes the most work. You can create a custom javascript function to manipulate the X-axis labels in SVG. More details on this here by #jeffery_the_wind:
/*
*
* The following 2 functions are a little hacky, they have to be done after calling the "draw" function
* The bubble chart originally displays only numbers along the x and y axes instead of customer or product names
* These 2 functions replace those numbers with the words for the customers and products
*
*/
for ( var i = -2; i < products.length + 1; i ++ ){
$('#customer_product_grid svg text[text-anchor="start"]:contains("'+i+'")').text(function(j,t){
if (t == i){
if (i >= products.length || i < 0){
return " ";
}
return products[i];
}
});
}
for ( var i = -2; i <= customers.length + 3; i ++ ){
$('#customer_product_grid svg text[text-anchor="end"]:contains("'+i+'")').text(function(j,t){
if (i >= customers.length + 1 || i <= 0){
return " ";
}else if (t == i){
return customers[i-1];
}
});
}

Google's documentation on customizing axes describes how to do what you're asking. You can change the type of your column to a string and populate with formatted Date strings.

Related

How to smoothly increase a number to Y in X milliseconds exponentially

I'm making a semi-realistic 'physics' engine in node.js, if you can even call it that and I want to accelerate exponentially. E.g. from 0m/s to 4.5m/s in 2 seconds, then maybe decelerate to 0m/s in 3 seconds. Obviously for the deceleration part I can probably get away with inputting a negative number.
Here's a picture of what I'm thinking of, not sure if what I expect in the graph is the same thing as exponents.
I don't have any code, I thought I could base it off something like setInterval, but that would be linear.
You're right SetInterval can only provide with a fixed speed whereas what you need is dynamic speed.
One way is to make two arrays with an equal number of items. With first array named duration and second name speed. The variable Y will be changed by the speed for the duration corresponding to the # of the speed. See here :
var Y = 0; // The variable that is to be changed
var speed = [4.5, 0, -4.5]; // The acceleration in m/s
var time = [2, 4, 2]; // Duration corresponding to each acceleration in s
var sec = 1000; // 1 second = 1000 milliseconds
var i = 0; // A counter to remember the item number in the arrays
var dur = 0; // A variable to hold the duration for which a particular speed has currently lasted. This variable helps to validate the duration for which the Y is changed.
// The function that holds the logic
function tick() {
// Checking if duration is equal to or has crossed the user specified duration for the speed
if(dur >= time[i]*1000){
// If yes, move on to the next speed and duration
i++;
dur = 0;
if(i > speed.length-1){clearInterval(loop);} // If there are no more items in the array stop the interval
}
else {
// If not, continue increasing Y and dur
Y = Math.round(Y*1000 + (speed[i]/50)*1000)/1000 // A simple workaround to avoid the error in arthimetic calculation that occurs while using floats (decimal numbers) in JS.
console.log(Y); // This line is only for debugging purposes. IT CAN BE REMOVED
dur += sec/50;
}
}
var loop = setInterval(tick, sec/50);

x axis on c3 chart shows only first and last value

I'm using c3 chart for js. My code is below:
c3.generate({
bindto:'#someChart',
data: {
columns: [
data
]
},
axis: {
x : {
type: 'categories',
categories:categories,
tick:{
count: 12
}
}
},
point:{
show:false
}
});
I have one problem. On x axis is showed only first and last value(12th).
It's a known bug in c3 where if the tick is due to occur at a fractional x value (i.e. the number of ticks means it should pop up say every 2.06666 categories) then it doesn't render --> https://github.com/c3js/c3/issues/1638
There's a fix that's offered there to run before you generate your chart -->
c3.chart.internal.fn.categoryName = function (i) {
var config = this.config, categoryIndex = Math.ceil(i);
return i < config.axis_x_categories.length ? config.axis_x_categories[categoryIndex] : i;
};
But I find while it now shows the right number of ticks, it often still doesn't line up the labels and ticks nicely to the data points (they're positioned partway in between).
On that point, it's better, if you can, to set the number of ticks (-1) to divide without a fraction into the number of data points (-1) you're wanting to show and they'll both avoid your initial issue and line up nicely.
e.g. (datapoints - 1)/(no.of.ticks - 1) == whole number

How to use d3.domain/range to have a custom domain

I'm currently using the whole idea of
var myQuantizeFunction = d3.scale.quantize()
.domain(minMaxFromData) // the minmax using d3.extent
.range(['bla-1', 'bla-2', 'bla-3', 'bla-4', 'bla-5']);
So this works fine when you want to generate a legend across your min-max. The issue is, I have some data which comes back as 0.
Here is an example legend for context :
As you can see, it's first or lowest value from the range is 0 - 4.7, what I want to really do is have 0 (ie none) as it's own legend item and have everything above ie 1 - 33 in this case as the other ranges.
I want to be able to specify that the first range is 0 and then the domain is split equally between values > 0.
Is there a d3 way of doing this? I'm sure someone else must have had this same problem before, I can't seem to find it but I may not be using the right search terms.
From the documentation:
quantize.domain([numbers])
If numbers is specified, sets the scale's input domain to the
specified two-element array of numbers. If the array contains more
than two numbers, only the first and last number are used. If the
elements in the given array are not numbers, they will be coerced to
numbers; this coercion happens similarly when the scale is called.
Thus, a quantize scale can be used to encode any type that can be
converted to numbers. If numbers is not specified, returns the scale's
current input domain.
As the name suggests d3 is 'data driven' so ignoring parts of your data set is not part of its ethos.
You need to write your own function to generate the [numbers] array.
Try:
data = [0,0,2,1,4,6,7,8,4,3,0,0];
min = undefined;
data.forEach(function (v) {
if (v > 0) {
if (typeof(min) === 'undefined') {
min = v;
} else if (v < min) {
min = v;
}
}
})
var myQuantizeFunction = d3.scale.quantize()
.domain([min, d3.max(data)])
.range(['bla-1', 'bla-2', 'bla-3', 'bla-4', 'bla-5']);
I improved the latest solution to use d3.min() and added code to the test the quantize function. Also I added a small function to colorize the output.
Everything done in the d3 datadriven way
data = [0,0,2,1,4,6,7,8,4,3,0,0];
range = ['bla-1', 'bla-2', 'bla-3', 'bla-4', 'bla-5'];
//strip the first element
reducedRange = range.slice();
reducedRange.shift();
var myQuantizeFunction =
d3.scale.quantize()
.domain([d3.min(data), d3.max(data)])
.range(reducedRange);
var filterQuantize = function(d){
if(d==0){
return range[0];
}else{
return myQuantizeFunction(d);
}
}
var colorize = d3.scale.category10();
// To test this we will put all the data in paragraphs
d3.select('body').selectAll('p').data(data) .enter()
.append('p')
.text(function(d){return d+':'+filterQuantize(d);})
.style('color',function(d){return colorize(d)});
View this code runing
Hope this helps, good luck!
Update: I stripped zero out of the scale to treat it as a special case as you pointed in the comment.

when generating normally-distributed random values, what is the most efficient way to define the range?

FYI: random == pseudo-random
A. when generating uniformly-random numbers, I can specify a range, i.e.:
(Math.random()-Math.random())*10+5
//generates numbers between -5 and 15
B. generating a set of random values with a version of Gaussian-esque normal randomness:
//pass in the mean and standard deviation
function randomNorm(mean, stdev) {
return Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1))*stdev+mean);
}
//using the following values:
{
mean:400,
standard_deviation:1
//results in a range of 397-403, or +-range of 3
},
{
mean:400,
standard_deviation:10
//results in a range of 372-429, or +-range of 30
},
{
mean:400,
standard_deviation:25
//results in a range of 326-471, or +-range of 75
}
each one gives me a range of approximately standard_deviation*(+-3) (assuming I left the program running longer).
C. I can calculate this range as follows:
assuming I want a range from 300-500, so var total_range = 200;
my mean is 400, my +-range is total_range/2 (var r = 100)
so standard_deviation would be r/3 or in this case 33.333.
This seems to be working, but I have no idea what I'm doing with math so I feel like an idiot, this solution feels kludgy and not totally accurate.
My question:
is there some formula that I'm dancing around that can help me here? my requirements are as follows:
must be able to define a range of numbers accurately.
must be done in JavaScript, as efficiently as possible.
I think maybe I'm close but it's not quite there.
Subtracting two random numbers doesn't give you a normal distribution, it will give you numbers that decline linearly on both sides of zero. See the red diagram in this fiddle:
http://jsfiddle.net/Guffa/tvt5K/
To get a good approximation of normal distribution, add six random numbers together. See the green diagram in the fiddle.
So, to get normally distributed random numbers, use:
((Math.random() + Math.random() + Math.random() + Math.random() + Math.random() + Math.random()) - 3) / 3
This method is based on the central limit theorem, outlined as the second method here: http://en.wikipedia.org/wiki/Normal_distribution#Generating_values_from_normal_distribution
I wanted to have gaussian random numbers between 0 and 1, and after many tests (thanks to #Guffa answer too) I found this to be the best:
function gaussianRand() {
var rand = 0;
for (var i = 0; i < 6; i += 1) {
rand += Math.random();
}
return rand / 6;
}
And as a bonus:
function gaussianRandom(start, end) {
return Math.floor(start + gaussianRand() * (end - start + 1));
}

google visualizations align 0 axis with two different y-axes

I'm creating a combochart with google's visualization library. I'm charting a store's traffic and revenue over the course of a day. I have set my draw options to
var options = {
seriesType: "bars",
series:{0:{targetAxisIndex:0},1:{targetAxisIndex:1}},
vAxes:{0:{title: "Revenue"},1:{title: "Traffic"}},
hAxis: {title: "Time", showTextEvery: 1},
};
which sets up the Revenue on a different Y-axis than the traffic. A sample of the data might look like this:
var data = [
// Time Revenue Traffic
['10:00-10:30', '132.57', '33'],
['10:30-11:00', '249.23', '42'],
['11:00-11:30', '376.84', '37'],
[... etc ..]
];
the problem I'm having is that Traffic values will always be positive whereas Revenue could be a negative number if there were returns. If that happens my Revenue axis will start at a negative value like -50 while Traffic starts at 0 and the horizontal baselines don't line up. I would like to have it so that even if Revenue has values less than 0 it's 0 axis will line up with the Traffic 0 axis.
Here's an example to show what's happening. See how the Traffic 0 axis is on the same level as the Revenue's -50 axis. I would like to know how to raise the Traffic baseline to the same level as the Revenue 0 axis.
I have a method that I am reasonably certain will always produce axis values with the same 0 point (I haven't proved that it can't produce axes with different 0 points, but I haven't encountered any).
To start off, get the range of the two date series (for our purposes, column 1 is "revenue" and column 2 is "traffic"):
var range1 = data.getColumnRange(1);
var range2 = data.getColumnRange(2);
For each series, get the max value of the series, or 1 if the max is less than or equal to 0. These values will be used as the upper bounds of the chart.
var maxValue1 = (range1.max <= 0) ? 1 : range1.max;
var maxValue2 = (range2.max <= 0) ? 1 : range2.max;
Then calculate a scalar value relating the two upper bounds:
var scalar = maxValue2 / maxValue1;
Now, calculate the lower bounds of the "revenue" series by taking the lower of range1.min and 0:
var minValue1 = Math.min(range1.min, 0);
then multiply that lower bound by the scalar value to get the lower bound of the "traffic" series:
var minValue2 = minValue1 * scalar;
Finally, set the vAxis minValue/maxValue options for each axis:
vAxes: {
0: {
maxValue: maxValue1,
minValue: minValue1,
title: 'Revenue'
},
1: {
maxValue: maxValue2,
minValue: minValue2,
title: 'Traffic'
}
}
The net result is that positive and negative proportions of each series are equal (maxValue1 / (maxValue1 - minValue1 == maxValue2 / (maxValue2 - minValue2 and minValue1 / (maxValue1 - minValue1 == minValue2 / (maxValue2 - minValue2), which means the chart axes should end up with the same positive and negative proportions, lining up the 0's on both sides.
Here's a jsfiddle with this working: http://jsfiddle.net/asgallant/hvJUC/. It should work for any data set, as long as the second data series has no negative values. I'm working on a version that will work with any data sets, but this should suffice for your use case.

Categories