Ajax / JSON Redrawing google chart - ajax

Im using the google visualization chart api here: https://developers.google.com/chart/interactive to make a server uptime graph which seems to be working nicely.
However I want the users to be able to select a date range and then redraw the graph without having to refresh the browser. And I have a small problem with this.
I first draw the graph with the initial data, and then if a user changes the date range this graph should be redrawn. I tried redrawing with some sample data and this works fine. However I cant seem to get it to work with the updated data.
Now in the php file where i fetch the data from the DB i return both the average uptime for this period as well as the total uptime for the period as such:
/*mysql query striped*/
$uptime_result = mysql_query($query, $connection) or die(mysql_error());
$uptime_data = "['Day', 'Uptime'],";
while ($items = mysql_fetch_array($uptime_result)){
$uptime_data.="['{$items['date']}',{$items['uptime']}], ";
}
// get average uptime
/*mysql query striped*/
$uptime_result = mysql_query($query, $connection) or die(mysql_error());
$result_array = mysql_fetch_array($uptime_result);
$avg_uptime = round($result_array['AVG(uptime)'],2);
echo "{\"data\":\"{$uptime_data}\",\"average\":$avg_uptime}";
Which outputs something like:
{"data":"['Day', 'Uptime'],['2012-05-31',100.00], ['2012-06-01',100.00], ['2012-05- 22',99.65], ['2012-05-21',99.65], ['2012-05-20',100.00], ['2012-05-31',100.00], ['2012-05-30',100.00], ['2012-05-29',100.00], ['2012-05-28',100.00], ['2012-05-27',100.00], ['2012-05-26',100.00], ['2012-05-25',100.00], ['2012-05-24',100.00], ['2012-05-23',100.00], ['2012-05-19',100.00], ['2012-05-18',100.00], ['2012-05-17',100.00], ['2012-05-16',100.00], ['2012-05-15',100.00], ['2012-05-14',100.00], ['2012-05-13',100.00], ['2012-05-12',100.00], ['2012-05-11',100.00], ['2012-05-10',100.00], ['2012-05-09',100.00], ['2012-05-08',100.00], ['2012-05-07',100.00], ['2012-06-02',100.00], ['2012-06-03',100.00], ['2012-06-04',100.00], ","average":99.98}
I.e a JSON array with two variables data and average. I am able to fetch the two independently as such:
$(function(){
$('#from,#to').focusout(function(){
var start=$('#from').val();
var end=$('#to').val();
$.ajax({
type: 'POST',
data: ({from : start, to : end, id : <?php echo $id; ?>}),
url: 'fetchuptime.php',
success: function(data) {
//7 reulst goes here
//var json = data;
var obj = jQuery.parseJSON(data);
$('#uptime_span').html(obj.average +" %");
$('#test').html(data);
chart_data = google.visualization.arrayToDataTable([
obj.data
]);
var ac = new google.visualization.AreaChart(document.getElementById('visualization'));
ac.draw(chart_data, {
colors : ['#00DB00'],
title : '',
isStacked: false,
width: 570,
height: 400,
'chartArea': {'width': '88%', 'height': '90%'},
hAxis: {minValue: 0,showTextEvery: 6, slantedText: false},
vAxis: {
viewWindowMode:'explicit',
viewWindow:{
max:100,
min:90.65
}
},
pointSize: 3,
legend: {position: 'none'}
});
}
});
});
});
eg. obj.average and obj.data gives me the two string. However this does not seem to work, i guess the data doesn't get passed along correctly.
I have tested the actual output data (eg obj.data) is formatted correct as I've tried inserting it statically.
So I'm obviously doing something wrong here, and I assume it's because I'm passing an string while google chart needs an array, tried to fix it in various ways but haven't found anything working yet.
Can anyone help me with this one?

The format of your JSON is valid, but probably not what you're wanting:
{"data":"['Day', 'Uptime'],['2012-05-31',100.00] ... ['2012-06-04',100.00], ", "average":99.98}
That represents an object with a field named data whose value is a string (like you said). What you probably want to do is make it an array on the server side. Instead of double quotes, use square brackets. There is also a trailing comma which must be removed.
{"data":[['Day', 'Uptime'],['2012-05-31',100.00] ... ['2012-06-04',100.00]], "average":99.98}

Related

Using L.esri.DynamicMapLayer, is it possible to bind a mouseover event rather than a pop-up on a dynamic map?

I'm aware of binding a pop-up to ESRI's L.esri.DynamicMapLayer here. The following code below is successful.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindPopup(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
var obj = featureCollection.features[0].properties;
var val = obj['Pixel Value'];
var lat = featureCollection.features[0].geometry.coordinates[1];
var lon = featureCollection.features[0].geometry.coordinates[0];
new L.responsivePopup({
autoPanPadding: [10, 10],
closeButton: true,
autoPan: false
}).setContent(parseFloat(val).toFixed(2)).setLatLng([lat, lon]).openOn(map);
}
});
}
});
But rather than a click response I am wondering as to whether you can mouseover using bindTooltip instead on a dynamic map. I've looked at the documentation for L.esri.DynamicMapLayer which says it is an extension of L.ImageOverlay. But perhaps there is an issue outlined here that I'm not fully understanding. Maybe it is not even related.
Aside, I've been testing multiple variations of even the simplest code to get things to work below but have been unsuccessful. Perhaps because this is asynchronous behavior it isn't possible. Looking for any guidance and/or explanation(s). Very novice programmer and much obliged for expertise.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindTooltip(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
new L.tooltip({
sticky: true
}).setContent('blah').setLatLng([lat,lng]).openOn(map);
}
});
}
});
Serendipitously, I have been working on a different problem, and one of the byproducts of that problem may come in handy for you.
Your primary issue is the asynchronous nature of the click event. If you open up your map (the first jsfiddle in your comment), open your dev tools network tab, and start clicking around, you will see a new network request made for every click. That's how a lot of esri query functions work - they need to query the server and check the database for the value you want at the given latlng. If you tried to attach that same behavior to a mousemove event, you'll trigger a huge number of network requests and you'll overload the browser - bad news.
One solution of what you can do, and its a lot more work, is to read the pixel data under the cursor of the image returned from the esri image service. If you know the exact rgb value of the pixel under the cursor, and you know what value that rgb value corresponds to in the map legend, you can achieve your result.
Here is a working example
And Here is the codesandbox source code. Don't be afraid to hit refresh, CSB is little wonky in the way it transpiles the modules.
What is happening here? Let's look step by step:
On map events like load, zoomend, moveend, a specialized function is fetching the same image that L.esri.dynamicMapLayer does, using something called EsriImageRequest, which is a class I wrote that reuses a lot of esri-leaflet's internal logic:
map.on("load moveend zoomend resize", applyImage);
const flashFloodImageRequest = new EsriImageRequest({
url: layer_url,
f: "image",
sublayer: "3",
});
function applyImage() {
flashFloodImageRequest
.fetchImage([map.getBounds()], map.getZoom())
.then((image) => {
//do something with the image
});
}
An instance of EsriImageRequest has the fetchImage method, which takes an array of L.LatLngBounds and a map zoom level, and returns an image - the same image that your dynamicMapLayer displays on the map.
EsriImageRequest is probably extra code that you don't need, but I happen to have just run into this issue. I wrote this because my app runs on a nodejs server, and I don't have a map instance with an L.esri.dynamicMapLayer. As a simpler alternative, you can target the leaflet DOM <img> element that shows your dynamicMapLayer, use that as your image source that we'll need in step 2. You will have to set up a listener on the src attribute of that element, and run the applyImage in that listener. If you're not familiar with how leaflet manages the DOM, look into your elements tab in the inspector, and you can find the <img> element here:
I'd recommend doing it that way, and not the way my example shows. Like I said, I happened to have just been working on a sort-of related issue.
Earlier in the code, I had set up a canvas, and using the css position, pointer-events, and opacity properties, it lays exactly over the map, but is set to take no interaction (I gave it a small amount of opacity in the example, but you'd probably want to set opacity to 0). In the applyImage function, the image we got is written to that canvas:
// earlier...
const mapContainer = document.getElementById("leafletMapid");
const canvas = document.getElementById("mycanvas");
const height = mapContainer.getBoundingClientRect().height;
const width = mapContainer.getBoundingClientRect().width;
canvas.height = height;
canvas.width = width;
const ctx = canvas.getContext("2d");
// inside applyImage .then:
.then((image) => {
image.crossOrigin = "*";
ctx.drawImage(image, 0, 0, width, height);
});
Now we have an invisible canvas who's pixel content is exactly the same as the dynamicMapLayer's.
Now we can listen to the map's mousemove event, and get the mouse's rgba pixel value from the canvas we created. If you read into my other question, you can see how I got the array of legend values, and how I'm using that array to map the pixel's rgba value back to the legend's value for that color. We can use the legend's value for that pixel, and set the popup content to that value.
map.on("mousemove", (e) => {
// get xy position on cavnas of the latlng
const { x, y } = map.latLngToContainerPoint(e.latlng);
// get the pixeldata for that xy position
const pixelData = ctx.getImageData(x, y, 1, 1);
const [R, G, B, A] = pixelData.data;
const rgbvalue = { R, G, B, A };
// get the value of that pixel according to the layer's legend
const value = legend.find((symbol) =>
compareObjectWithTolerance(symbol.rgbvalue, rgbvalue, 5)
);
// open the popup if its not already open
if (!popup.isOpen()) {
popup.setLatLng(e.latlng);
popup.openOn(map);
}
// set the position of the popup to the mouse cursor
popup.setLatLng(e.latlng);
// set the value of the popup content to the value you got from the legend
popup.setContent(`Value: ${value?.label || "unknown"}`);
});
As you can see, I'm also setting the latlng of the popup to wherever the mouse is. With closeButton: false in the popup options, it behaves much like a tooltip. I tried getting it to work with a proper L.tooltip, but I was having some trouble myself. This seems to create the same effect.
Sorry if this was a long answer. There are many ways to adapt / improve my code sample, but this should get you started.

GDocs ImportHTML/XML not getting proper data

I have a simple Google Spreadsheet's question. I'm attempting to pull in a table and am not having luck with ImportHTML, it will only pull the first top cell and duplicates it somehow. I also tried ImportXML, using XPath Helper to get the proper XPath, and it will not load the proper table data either.
Google Doc:
https://docs.google.com/spreadsheets/d/1-toAivOhywZuErHK0LB5EADiMu_T9ZNUTgMhqFRBHOU/edit?usp=sharing
What I'm needing is the bottom table(id='player_gbs') on the following site:
http://www.forge-db.com/us/us18/players/profile/?server=us18&world=Sinerania&id=12497
Code Snippet
Here is what I've tried so far, this is all represented in the GDoc as well.
=ImportHTML(B1, "table", 2)
Returns the following line twice:
"Great Building Era Lvl FP Req. FP FP Left 24h +"
=ImportXML(B1, "/html/body/div[#class='wrap']/div[#class='content'][2]/div[#class='b-box']")
Returns:
"GB's with a new level in the last 24 hours are shown with a yellow
background" Great BuildingEraLvlFPReq. FPFP Left24h +Great BuildingEraLvlFPReq. FPFP Left24h +"
Thinking the issue is that contained in the /div is <thead> and <tfoot> before <tbody> so I tried this XPath and just get N/A:
=ImportXML(B1, "/html/body/div[#class='wrap']/div[#class='content'][2]/div[#class='b-box']/div[#id='player_gbs_wrapper']/table[#id='player_gbs']/tbody")
I believe your problem is that that table is created via JSON and javascript. If you view source on that page you'll see this chunk ...
<script type="text/javascript" class="init">
$(document).ready(function() {
$('#player_gbs').dataTable( {
"aLengthMenu": [[30], ['All']],
"processing": true,
"serverSide": true,
"ajax": "../../getPlayerGBs.php?id=12497&server=us18",
Which tells us that the data is coming from the following URL.
http://www.forge-db.com/us/us18/getPlayerGBs.php?id=12497&server=us18
That URL is providing the data that populates the table.
This script (based off this SO response) will parse the data from that feed and write it to a sheet titled dataImport. It only gets the first two chunks of data, you'd just extend the loop to do more.
function urlDownload() {
var dataImport = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('dataImport');
var apiPoint = "http://www.forge-db.com/us/us18/getPlayerGBs.php?id=12497&server=us18";
var response = UrlFetchApp.fetch(apiPoint);
var response_json = JSON.parse(response.getContentText());
var length = response_json.data.length;
var a = [];
for(i=0; i<length; i++){
dataImport.getRange(i+2, 1, 1, 1).setValue(response_json.data[i][0])
dataImport.getRange(i+2, 2, 1, 1).setValue(response_json.data[i][1])
}
}

Kendo UI: Excel Export not working correctly after datasource refreshing data

I have a Grid, when users click a button, it gets some parameters and refresh datasource:
var grdUP = $("#weblogGrid").data("kendoGrid");
grdUP.dataSource.transport.options.read.url = url; // new url
//Read data source to update
grdUP.dataSource.read();
it works fine. the new data shows in the grid. And the grid has another button, which will export the data to excel. I'm using below code (also tried the built-in button):
var grid = $("#weblogGrid").data("kendoGrid");
grid.saveAsExcel();
it actually exports the data to excel file.
However, it always exports the initial data in the grid, not the data user refreshed.
For example, when the grid first shows up, it has 10 rows data. After refresh, it has 5 rows data. Now, if export, it still exports 10 rows data although the data in grid is different.
Is this a Bug? or, maybe I did something wrong in refresh grid?
Thanks
===============================
edit to clarify something
Thanks. currently, I got new data using:
var url = '/WeblogReport/GetWebLogList?fromDate=' + fromDate + '&toDate=' + toDate;
var grdUP = $("#myGrid").data("kendoGrid");
//Set url property of the grid data source
grdUP.dataSource.transport.options.read.url = url;
//Read data source to update
grdUP.dataSource.read();
So I changed to:
// get value of date
....
$.ajax({
type: "GET",
dataType: "json",
url: "/WeblogReport/GetWebLogList",
data: { FromDate: fromDate, ToDate: toDate },
success: function (data) {
alert(data);
var grid = $("#myGrid").data("kendoGrid");
grid.dataSource.data(data);
grid.refresh();
}
});
Somehow, it does not show the new data. Any suggestions?
Thank you very much.
add more clarification
Here is in the Json call.
success: function (data) {
var newdata = [{ "UserName": "username", "ClientIP": "1.1.1.1"}];
$("#myGrid").data("kendoGrid").dataSource.data(newdata);
$("#myGrid").data("kendoGrid").refresh();
//$("#myGrid").data("kendoGrid").saveAsExcel();
}
Set both of the following fields to make Excel export work:
grid.dataSource.transport.options.read.url = url;
grid.options.dataSource.transport.read.url = url;
check this:
http://jsfiddle.net/Sowjanya51/o8cw3vj8/
$('#grid1').data('kendoGrid').dataSource.data(newdata);
$('#grid1').data('kendoGrid').refresh();
You need to update the dataSource and reload the grid,otherwise the grid dataSource will still have reference to old data even though UI displays the new data.
Just set the "allPages" option on your grid to "True". like so :
excel: {
fileName: "Export.xlsx",
filterable: true,
allPages: true
},
Your original solution should be fine if you refresh the grid after you read from the data source.
var grdUP = $("#weblogGrid").data("kendoGrid");
grdUP.dataSource.transport.options.read.url = url; // new url
//Read data source to update
grdUP.dataSource.read();
//add this line to refresh the active data set in the grid
grdUP.refresh();
I had run into the same issue and this solved it for me. The only difference between your approach and mine is that you're changing the data source's read URL, whereas I was changing the data parameters for the read method. Shouldn't make any difference, but I'll mention that just in case.

There must be an easier way

I am trying to create an JQM app and are doing so by getting a lot of data from database. When I click on a link from a ajax/json generated calendar list I should then be able to get the info for that event by calling the server and get the data. As it is now I do this in 2 steps like this.
My ajax generated event list:
$.each(groupcalendar, function (i, val) {
output += '<li><h2>' + val.matchformname + '</h2><p><strong>' + val.matchday + '</strong></p><p>' + val.coursename + '</p><p class="ui-li-aside"><strong>' + val.matchtime + '</strong></p></li>';
});
When I click on one of the links I want to goto a page called prematchdata.html and get the data fro that specific event. I do so by first calling the click and get the eventid from data-id like this:
$(document).on('click', '#gotoMatch', function () {
var matchid = $(this).attr("data-id");
$.get("http://mypage.com/json/getmatchinfo.php?matchid="+matchid, function(data) {
localStorage["matchinfo"] = JSON.stringify(data);
$.mobile.changePage( "prematchdata.html", { transition: "slide", changeHash: true} );
}, "json");
});
I save the returned data as localStorage and then uses this data in my pageinit like this:
$(document).on("pageinit", "#prematchdata", function() {
var matchinfo = {};
matchinfo = JSON.parse(localStorage["matchinfo"])
var content = '<h2>'+matchinfo["matchname"]+'</h2>';
$('.infoholder').html(content);
});
It works, although for me it seems like the last 2 steps should be done in one, but i am not sure how to do so? It seems a little bit wrong get data, save locally and then use it? Can't this be done without the $(document).on('click', '#gotoMatch', function () {});?
Hoping for some help and thanks in advance :-)
You could try sending it up using a query string. When you're using changePage, change your code like this :
$(document).on('click', '#gotoMatch', function () {
var matchid = $(this).attr("data-id");
$.get("http://mypage.com/json/getmatchinfo.php?matchid=" + matchid, function (data) {
paramData = data[0];
$.mobile.changePage("prematchdata.html", {
transition: "slide",
changeHash: true,
data: paramData //added this extra parameter which will pass data as a query string
});
}, "json");
});
When you're getting it back,
$(document).on("pageinit", "#prematchdata", function() {
var url = $.url(document.location);
var name= url.param("matchname");
var content = '<h2>'+ name +'</h2>';
$('.infoholder').html(content);
});
Another easy way would be use a singlepage template instead of a multi page template. Then, you could just use a global variable to get and set data.
That said, what you're doing right now is more secure than this query string method. By using this, anyone can see what you are sending over the URL. So I advise you keep using localStorage. For more info on this, look into this question.

jquery long polling: prevent update until data actually changes

I have created a script that long polls a JSON source and updates a div with the results. My code is posted below.
The code works fine and the data I request is grabbed and outputted correctly. The code checks the JSON and grabs 2 images and some text data.
The problem is it keeps refreshing the data and downloading the images constantly causing high server load and bandwidth consumption (obviously this is small right now but will increase as I complete my project). This also leads ot me not being able to select the text in the div and the images flicker as they are reloaded, both are undesirable consequences of my current code.
I want to be able to grab the data and display it, and not update it at all until the data actually changes in the JSON response, I am assuming I need to do something with a timestamp?. I have tried creating a lastupdate timestamp by using the first_aired key from the JSON but it is not working, I am unsure if I have made a mistake or if I am barking up the wrong tree.
Could someone take a look at the code I have and perhaps point me in the correct direction as to what I need to do?
var lastupdate = 0;
// call getData when the document has loaded
$(document).ready(function(){
getData(lastupdate);
});
var getData = function(lastupdate) {
$.ajax({
type: "GET",
url: 'http://api.trakt.tv/user/watching.json/apikey/user/lastupdate='+lastupdate+'&callback=?',
dataType: 'jsonp',
async: true,
cache: false,
// timeout after 5 minutes, shut process down and restart
timeout:300000,
// process a successful response
success: function(watching_now) {
if (!jQuery.isEmptyObject(watching_now)) {
//console.log(watching_now);
var airDate = watching_now.episode.first_aired;
var showTitle = watching_now.show.title;
var showPoster = watching_now.show.images.poster;
var showURL = watching_now.show.url;
var episodeTitle = watching_now.episode.title;
var episodeScreen = watching_now.episode.images.screen;
var episodeNumber = watching_now.episode.number;
var episodeSeason = watching_now.episode.season;
$('#watching-now').html('<div class="screencap"><img src="' + episodeScreen +' " width="240" height="150" /></div><div class="poster"><img src="' + showPoster +'" width="85" height="120" /></div><div class="watching-info">'+episodeSeason+'x'+episodeNumber+' - '+episodeTitle+'</div>')
}
else {
$('#watching-now').html('You are not currently watching anything')
}
// set lastupdate
lastupdate = watching_now.airDate;
// call again in 1 second
setTimeout('getData('+lastupdate+');', 1000);
},
// handle error
error: function(XMLHttpRequest, textStatus, errorThrown){
// try again in 10 seconds if there was a request error
setTimeout('getData('+lastupdate+');', 10000);
},
});
};
Here is the JSON I am getting the information form:
{"type":"episode","action":"watching","show":{"title":"Stargate Atlantis","year":2004,"url":"http://trakt.tv/show/stargate-atlantis","imdb_id":"tt0374455","tvdb_id":"70851","tvrage_id":"5324","first_aired":1089961200,"country":"United States","overview":"The story of Stargate Atlantis follows the cliffhanger episode on Stargate SG-1's seventh season finale \"Lost City\", where SG-1 found an outpost made by the race known as the Ancients in Antarctica. After the events of Stargate SG-1 season eight premiere \"New Order\", the Stargate Command sends an international team to investigate the outpost. Soon, Dr. Daniel Jackson discovers the location of the greatest city created by the Ancients, Atlantis. The story unfolds when the members of the expedition encounter the Wraith, the race that defeated the Ancients ten thousand years ago.","runtime":60,"network":"Syfy","air_day":"Monday","air_time":"9:00pm","certification":"TV-PG","images":{"poster":"http://trakt.us/images/posters/329.3.jpg","fanart":"http://trakt.us/images/fanart/329.3.jpg","banner":"http://trakt.us/images/banners/329.3.jpg"},"genres":["Action","Adventure","Science Fiction"]},"episode":{"season":3,"number":10,"title":"The Return (1)","overview":"The Atlantis expedition is stunned to learn that a ship full of Ancients is returning to reclaim their lost city. ","first_aired":1158908400,"url":"http://trakt.tv/show/stargate-atlantis/season/3/episode/10","images":{"screen":"http://trakt.us/images/episodes/329-3-10.3.jpg"}}}
If you need more information please just ask and I will provide everything I can.
Cheers
Take a look at example shown in link below.
http://www.zeitoun.net/articles/comet_and_php/start
what it does is pass time-stamp and get the record between current time-stamp and passed time-stamp.

Resources