I have a jqplot chart that has two lines. I'm using tooltipContentEditor to modify the text displayed in the tooltip. Prior to adding the second line, the tooltips displayed the values fine. Now that I have added another line, the tooltips always show the data for the second line (thus incorrectly for the first). How do I have the correct values displayed?
Blue line tooltip shows yellow line value:
Chart initialization (tooltipContentEditor function inside):
$.jqplot('mychart', [coords, paidCoords], {
//title:'Sales for ' + name,
animate: !$.jqplot.use_excanvas,
axesDefaults: {
tickRenderer: $.jqplot.CanvasAxisTickRenderer ,
},
axes:{
xaxis:{
renderer:$.jqplot.DateAxisRenderer,
tickInterval: tickInterval,
tickOptions: { formatString: formatString, angle: -30 }
},
yaxis:{
min:0
}
},
highlighter: {
show: true,
sizeAdjust: 7.5,
tooltipContentEditor: function(str, pointIndex, index, plot){
console.log('logging in tooltipeditor');
console.log(str);
console.log(pointIndex);
console.log(index);
console.log(plot);
var splitted = plot._plotData[1][index];
var x = splitted[0];
var y = splitted[1];
x = new Date(x);
x = x.toString();
return x + "<br/>$" + y;
}
},
cursor:{
show: true,
zoom:true,
showTooltip:false
}
});
Nevermind! I figured it out! In the tooltipContentEditor function I needed to do this:
var splitted = str.split(",");
Instead of this:
var splitted = plot._plotData[1][index];
Related
So i'm currently trying to fix a problem with a Google Charts. I am drawing a chart that shows numeric values on the Y-axis and dates on the X-axis. I want the date to adpat according to the specified timezone. For that i'm using the DateFormatter Object from Google charts, providing the patter and the Timezone like so:
var dateFormatter = new google.visualization.DateFormat({
timeZone: _timeZone, //timezone
pattern: $scope.selectedResolution.pattern
});
The pattern attribute receives a string containing a format string and the timeZone receves a value representing the number of hours regarding the timezone offset (example, -4, -5, etc).
I Format the chart values like so:
dateFormatter.format($scope.data, 0);
Being that the column "0" is the column where i have my dates.
I then draw the chart, standard stuff:
chart = new google.visualization.ComboChart(document.getElementById(chart_name));
//generateTicks($scope.data, options);
chart.draw($scope.data, options);
The thing is that, the values showed in the X-Axis are not formatted whatsover, be it in the format or the timezon.
Problem Showcase
As you can see in the image above, the X-Axis shows "13:00" where it should show "15:00", in this particular case.
I also went and check the data i'm feeding the chart with to see if the formatter was actually doing any work on the data itself and it appears to be working properly:
Chart Data
Also, here's my charts options for reference:
var series = {}, firstSerie, row, title, tempRows = [];
// Create the data table
series[0] = {targetAxisIndex: 0, color: '#3182BD'};
series[1] = {color: '#FF7F00', type: 'line'};
options = {
tooltip: {isHtml: true},
titleTextStyle: {fontSize: 12, bold: false},
isStacked: false,
backgroundColor: 'transparent',
legend: {position: 'none'},
height: 300,
pointSize: 1,
series: series,
vAxes: {
// Adds titles to each axis.
0: {
textStyle: {color: $scope.widgetOptions.text_color || '#333333'},
format: $scope.vLabel === '%' ? '#\'%\'' : generateVerticalAxisPattern() + $filter('trustedHtml')($scope.vLabel),
minValue: 0,
gridlines: {
color: 'transparent'
}
}
},
hAxis: {
//title: graphLegend,
textStyle: {color: $scope.widgetOptions.text_color || '#333333'},
titleTextStyle: {color: $scope.widgetOptions.text_color || '#333333'},
viewWindows: {
min: moment().subtract(24, 'hours'),
max: moment()
},
gridlines: {
color: 'transparent',
units: {
hours: {format: ['HH:mm', 'ha']}
},
count: -1
},
minorGridlines: {
count: 0
}
},
vAxis: {
viewWindowMode: 'maximized',
viewWindow: {
min: $scope.widgetOptions.lower_limit
}
},
animation: {
duration: 500,
easing: 'out'
}
};
So, does anyone have any clue of what's going on? Im working with angularjs btw.
Thanks in advance
the format method, as follows, only formats the data, not the chart or chart axis.
dateFormatter.format($scope.data, 0);
basically, the above is used to display the correct format in the tooltip when hovering a data point.
to format an axis, you would normally set the format configuration option.
hAxis: {
format: $scope.selectedResolution.pattern
}
however, in this case, since you are changing the time zone,
you will need to provide custom ticks on the x-axis.
you can build an array of ticks from the data table rows,
using the formatValue method from the formatter.
var ticks = [];
for (var i = 0; i < $scope.data.getNumberOfRows(); i++) {
var dateValue = $scope.data.getValue(i, 0);
ticks.push({
v: dateValue,
f: dateFormatter.formatValue(dateValue)
});
}
// then add above to ticks to options...
hAxis: {
ticks: ticks
}
each tick will need to be an object,
with properties for v: for value,
and f: for formatted value.
the only problem with the above routine,
it assumes you want to display a tick for each row in the data table.
if you do not have room to display them all,
you will need to figure out your own algorithm to display the correct number of ticks.
edit
another method is to find the min and max dates within the data table,
using data table method getColumnRange
then build each tick after a particular duration.
for instance, to display a tick every four hours along the axis, between the min and max dates...
const FOUR_HOURS = (1000 * 60 * 60 * 4);
var ticks = [];
var dateRange = $scope.data.getColumnRange(0);
for (var i = dateRange.min.getTime(); i <= dateRange.max.getTime(); i = i + FOUR_HOURS) {
var dateValue = new Date(i);
ticks.push({
v: dateValue,
f: dateFormatter.formatValue(dateValue)
});
}
The Goal
I'm attempting to render a long series of data (around 200 ticks, from small float values like 1.3223) into a line chart.
The Issue
When I use a series of data that changes only a small amount (around 0.0001 every tick), the chart is rendered as very jagged (scissor like). I would like to somehow fix it to have a "saner" radius between each point on the graph.
A Good Example
On the other hand, when rendering higher values (around 1382.21) with bigger difference between ticks (from 0.01 to 0.05 +/-) the graph is rendered more smooth and aesthetically pleasing.
Edit: As user Arie Shaw pointed out, the actual low or high values don't make a difference and it remains an issue of representing small "monotonous" changes is a less jagged form.
The Code
var initChart = function(data, container) {
new Highcharts.Chart({
chart: {
type: "area",
renderTo: container,
zoomType: 'x'
},
title: {
text: ''
},
xAxis: {
labels: {
enabled: false
}
},
yAxis: {
title: {
text: ''
}
},
legend: {
enabled: false
},
color: '#A3D8FF',
plotOptions: {
area: {
fillColor: '#C6E5F4',
lineWidth: 1,
marker: {
enabled: false
},
shadow: false,
states: {
hover: {
lineWidth: 1
}
},
threshold: null
}
},
exporting: {
enabled: false
},
series: [{
name: "TEST",
data: data
}]
});
};
Both graphs, and sample data sets are presented in the following fiddle:
http://jsfiddle.net/YKbxy/2/
The problem you're experiencing is unavoidable: If you have a lot of small variations over time, the graph is going to appear jagged provided that you show each data point.
The key point is that last phrase.
One way to 'smooth out' the rough parts would be to average the data. For example:
var myData = []; //... Some array of data; assuming just numbers
var averageData = function (data, factor) {
var i, j, results = [], sum = 0, length = data.length, avgWindow;
if (!factor || factor <= 0) {
factor = 1;
}
// Create a sliding window of averages
for(i = 0; i < length; i+= factor) {
// Slice from i to factor
avgWindow = data.slice(i, i+factor);
for (j = 0; j < avgWindow.length; j++) {
sum += avgWindow[j];
}
results.push(sum / avgWindow.length)
sum = 0;
}
return results;
};
var var initChart = function(data, container) {
new Highcharts.Chart({
series: [{
name: "TEST",
data: averageData(myData, 2)
}]
});
});
This method also has the advantage that you could (potentially) reuse the function to compare the averaged data to the regular data, or even toggle between how much to average the data.
You can always use areaspline instead of area, see: http://jsfiddle.net/YKbxy/3/
why dont you treat you .00001 data as 1, so times 10000, and then write it in your legend like that.
You should even do that as a test, since if the chart looks fine then, it means there is a problem in the dataset numbers when you return it to normal, since high charts takes the difference between high and low...
Either you must approximate your data by only using a few decimal places or you must average out the values using something like:
var data = new Array(200);
var smallArray = new Array(5);
var averagedData = new Array(20);
for (var index=0; index<=averagedData.length; index++){
for(var i = 0; i<=smallArray.length; i++){
smallArray[i] = data[i + index * 5];
}
averagedData[index] = (smallArray[1] + smallArray[2] + smallArray[3] + smallArray[4] + smallArray[5])/smallArray.length;
}
Then you will only need to plot 20 averaged points on an array of 200 data points. You can change the values for what you need.
In the end the issue is in the frequency of the points or their plotting on yAxis.
When I provide more realistic positioning (e.g timestamp) it will look good.
Meaning that jaggedness is a result of the small changes over constant yAxis progression, which is most similar to nt3rp's answer
I am currently using highstock to plot the total number of items available based on time throughout the day (which then updates real-time).
If two changes to the total number of items happens at the same time, in highstock I get a vertical bar of the difference:
So in my example image we start with 4299 things, then 53 items are removed and 50 are added (technically at the same time, but are two different transactions and are two points). With a net difference of -3. (or in otherwords, I get {x: 5:44:15 and y: 4246, change: -53}, {x: 5:44:15, y: 4296, change: 50}).
So my question:
Is it possible in highstock to merge those points to get rid of the vertical bar and use 4296 as the shown value? I was hoping I could then use the tooltip formatter to loop through 'this.points' and display a change of -53 and a change of 50 in the tooltip so the user can see what resulted in a net change of -3.
If this is not possible, I will just merge the points myself and pass all the relevant information in the point to generate the tooltip (and chart look) that I am going for, but wanted to see if I could just utilize all the functionality of highstock first - and keep these points separate.
Thanks!
Edit::
new Highcharts.StockChart({
chart : {
renderTo : 'realTimeChart',
zoomType: 'x',
backgroundColor: '#feffdd',
style: {
fontFamily: 'Segoe UI'
},
type: 'spline'
},
plotOptions: {
area: { animation: false },
arearange: { animation: false },
areaspline: { animation: false },
areasplinerange: { animation: false },
bar: { animation: false },
column: { animation: false },
columnrange: { animation: false },
gauge: { animation: false },
line: { animation: false },
pie: { animation: false },
scatter: { animation: false },
series: { animation: false },
spline: { animation: false }
},
xAxis: {
ordinal: false
},
tooltip: {
animation: false,
formatter: function() {
var p = '';
p += '<span style="font-size: 9px;">' + Highcharts.dateFormat('%A, %b %e, %Y %H:%M:%S', this.x) +'</span><br/>';
$.each(this.points, function(i, point){
p += '<span style="color:' + this.series.color + '">' + this.series.name + '</span>: <b>'+ this.y +'</b>';
if (point.point.where) {
p += '<br />' + point.point.where + ' changed by ' + point.point.change + (point.point.who ? ' (' + point.point.who + ')' : '');
}
});
return p;
}
},
rangeSelector: {
buttons: [{
count: 30,
type: 'minute',
text: '30M'
}, {
count: 1,
type: 'hour',
text: '1H'
}, {
count: 6,
type: 'hour',
text: '6H'
}, {
type: 'all',
text: 'Day'
}],
inputEnabled: false,
selected: 1
},
exporting: {
enabled: false
},
series : [{
name : 'Available',
data : data,
lineWidth: 1,
states: {
hover: {
enabled: false
}
}
}]
Data is in the format I showed previously, except the x is actually in milliseconds since epoch:
data = [
{x: 123456789, y: 2000, where: 'Location', change: 40, who: 'Joe'},
{x: 123456789, y: 1960, where: 'Location', change: -40, who: 'Bob'},
...
];
Just wanted to follow up with how I easily got around the probem. Instead of placing by second, I decided to group points together to the nearest minute rounding down (so I have blocks of minutes).
Then for each point I passed in an array of the actual points contained within that minute block as a new argument, and updated the y value for that minute block. Then I used the tooltip formatter to display all the changes within that minute block with their actual time of change. This gave me a more flowing graph instead of all these hard vertical points for the same x-axis.
To easily change a data point at a specific x-axis point, I kept a separate array of the location of the minute block inside the series.data array for highcharts, that way if I needed to update a block, I knew exactly where that time series was.
Here is how I accomplished my task:
I create the reference array:
var pointIndex = {};
I created the inital data series from historical data for the day (pulled in via ajax):
var data = [];
var time = Math.floor(actual_time / 60000) * 60000;
pointIndex[time] = data.push({x: time, y: items_available, change: [{when: actual_time}]});
So actual_time is the number of milliseconds since epoch (when the even change occured), then I round that to the nearest minute to get the minute time block, change is the argument that will hold all the actual points for display in the tooltip.
So when I add a new point I check if the minute block exists, if it does not, add a new point, otherwise update an old point:
var time = (new Date()).getTime();
var point = Math.floor(time / 60000) * 60000;
if (pointIndex[point]) {
var change = chart.series[0].data[pointIndex[point]].change;
change.push({when: time});
chart.series[0].data[pointIndex[point]].update({x: point, y: items_available, change: change});
} else {
pointIndex[point] = chart.series[0].data.length;
chart.series[0].addPoint({x: point, y: items_available, change: [{when: time}]}, false, false);
}
(In all cases I do the actual chart refresh after I am done updating points.)
Hopefully that will help anyone else who finds theirself in the same position!
Edit:: (forgot the formatter):
tooltip: {
animation: false,
formatter: function() {
var p = '';
p += '<span style="font-size: 9px;">' + Highcharts.dateFormat('%A, %b %e, %Y %H:%M', this.x) +'</span><br/>';
$.each(this.points, function(i, point){
p += '<span style="color:' + this.series.color + '">' + this.series.name + '</span>: <b>'+ this.y +'</b>';
if (point.point.change) {
for(var j = 0; j < point.point.change.length; ++j) {
p += '<br />Change at: ' + new Date(point.point.change[j].when).toTimeString();
}
}
});
return p;
}
}
I'm trying to limit the number of ticks on a dynamic jqplot I'm setting up. Unfortunately, when I do it, the points do not look accurate. Here's what it looks like:
As you can see, the point on 10/05/12, for example, is not on it's correct axis. I realize that now these points are estimated, but the points must look accurate on the graph -- especially when hovering over the points gives you their correct date and y-axis value.
Here's my code for this intilization, if that helps:
var datepickerBegin = $("#datepicker_start").datepicker({dateFormat: "dd/mm/yyyy"}).val();
var datepickerEnd = $("#datepicker_to").datepicker({dateFormat: "dd/mm/yyyy"}).val();
var startDiff = $("#datepicker_start").datepicker("getDate");
var endDiff = $("#datepicker_to").datepicker("getDate");
alert(startDiff)
alert(endDiff)
// Check if dates are more than a week apart
if(endDiff - startDiff >= 15 * 86400 * 1000) {
var plot2 = $.jqplot('chart2', jsonurl,{
title: "Storage Space",
dataRenderer: ajaxDataRenderer,
dataRendererOptions: {unusedOptionalUrl: jsonurl},
axes: {
xaxis: {
'numberTicks' : 15,
min: datepickerBegin,
max: datepickerEnd,
renderer:$.jqplot.DateAxisRenderer,
rendererOptions:{tickRenderer:$.jqplot.CanvasAxisTickRenderer},
tickInterval: '1 day',
tickOptions:{formatString:'%#m/%#d/%Y'
}
},
yaxis: {
label: "MB",
tickOptions:{formatString:'%d '},
min: 0
}
},
highlighter: {
show: true,
sizeAdjust: 7.5
}
});
// Somehow fix the line?
plot2.redraw({});
}
Anyone out there have a fix for this? Thanks!
I am having difficulty showing a string separated with dates on a basic high chart
I am getting and saving the tweets from twitter into arrays and then converting the array into a string separated by commas and quotes.
Unfortunately I cant display them on the graph, I have no idea what I'm doing wrong.
function search(){
var value = $('#box').val();
var array=[];
var dateArray = [];
var dateString;
if (value!==""){$.getJSON("",
function(data){
$.each(data.results, function(i, item){
var user=item.from_user;
var created_at=new Date(item.created_at);
var month = created_at.getMonth();
var day = created_at.getDate();
var year = created_at.getFullYear();
var created= day +'/'+ month+ '/'+ year;
array.push({date:created,username:user});
});
// console.log(array);
for (var i in array) {
dateArray.push(array[i].date);
}
dateString="'" + dateArray.join("','") + "'";
console.log(dateString);
});
}
var chart;
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'line',
marginRight: 130,
marginBottom: 25
},
title: {
text: 'Monthly Average Temperature',
x: -20 //center
},
subtitle: {
text: 'Source: WorldClimate.com',
x: -20
},
xAxis: {
categories: [dateString]
},
Fixed JsFiddle
Your x-axis parameter should be as follows:
xAxis: {
categories: dateString
},
Als, just change this :-)
for (var i in array) {
dateString.push(array[i].date);
}
highchart(dateString);
Oh, and you should change to this, definately.
<input type="text" id="box" value="a"/>
Extra investigation info:
It appeared that (in the old situation)
console.log(stringArray);
highchart(stringArray);
was empty. Would you pass
highchart(["ab", "b", "c"]);
then you'd be fine. This is because you are passing an empty array, you are creating the chart before you have the JSON data. I therefore moved the creation to the JSON function.
Why not use xAxis as datetime:
xAxis: {
type: 'datetime'
},
Parse the dates into JS time from the twitter data and have something like this as your series data array:
[[<jstime1>, "big cool tweet1"], [<jstime2>, "big cool tweet2"]]