How do I set a date range variable dynamically and redraw a Google chart?
Asked Answered
L

1

7

I am using PHP to set a date range for creating a Google Line Chart. For each date in the range a variable is set ($running_balance) to create the points on the line chart using data in a database. I would like to be able to set the variable $end, which essentially determines the date range, dynamically, but I am not sure how to do this so that the chart would be redrawn according to this new range. I am aware that I could create a new function that includes drawChart(); to redraw the chart, and I would be using three buttons to either set the date range to 1 year, 3 months, or 1 month, but I am not sure how to put all this together. Here is the code that I currently have:

$begin = new DateTime(date('Y-m-d', strtotime('+1 days')));
$end = new DateTime(date('Y-m-d', strtotime('+365 days')));
$interval = DateInterval::createFromDateString('1 day');
$period = new DatePeriod($begin, $interval, $end);

foreach ( $period as $dt ) {

$date_display = $dt->format("D j M");

.....  code to generate $running_balance .....

$temp = array();

    $temp[] = array('v' => (string) $date_display); 
    $temp[] = array('v' => (string) $running_balance);
    $temp[] = array('v' => (string) $running_balance);
    $rows[] = array('c' => $temp);
}

$table['rows'] = $rows;
$jsonTable = json_encode($table);

<script type="text/javascript">

    // Load the Visualization API and the piechart package.
    google.load('visualization', '1', {'packages':['corechart']});

    // Set a callback to run when the Google Visualization API is loaded.
    google.setOnLoadCallback(drawChart);

    var table = <?php echo $jsonTable; ?>;

    function drawChart() {
    var data = new google.visualization.DataTable(table);

      // Create our data table out of JSON data loaded from server.
        //  var data = new google.visualization.DataTable(<?=$jsonTable?>);
      var formatter = new google.visualization.NumberFormat({fractionDigits:2,prefix:'\u00A3'});
      formatter.format(data, 1);
      var options = {
          pointSize: 5,
          legend: 'none',
          hAxis: { showTextEvery:31 },
          series: {0:{color:'2E838F',lineWidth:2}},
          chartArea: {left:50,width:"95%",height:"80%"},
          backgroundColor: '#F7FBFC',
          height: 400
        };
      // Instantiate and draw our chart, passing in some options.
      //do not forget to check ur div ID
      var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
      chart.draw(data, options);
    }

</script>
Laity answered 7/4, 2013 at 22:24 Comment(1)
You should make (at least) two separate functions. Function #1 will retrieve data. Function #2 will call the data-retrieving function and draw the chart. When you click a button/change a dropdown, it should call a handler function that will get the appropriate data and send it to the chart drawing function. Basically, take the first half of your code (before drawChart()) and turn it in to a function that accepts the $end parameter. Have you tried something like that already?Ways
G
7

OK, if I'm understanding you correctly, you're having trouble conceiving and designing what parts of these actions are server-side (PHP) and what parts are client side (Javascript) and then the client-server communication strategy. This is a common speedbump. There's several ways to deal with it.

First (and less preferred) you could create a form and reload the whole page with the new date range:

// we're looking for '+1 year', '+3 months' or '+1 month'. if someone really
// wants to send another value here, it's not likely to be a security risk
// but know your own application and note that you might want to validate
$range = isset($_GET['range'])&&$_GET['range']?$_GET['range']:'+1 year';

$begin = new DateTime(date('Y-m-d', strtotime('+1 days')));
$end = new DateTime(date('Y-m-d', strtotime($range)));
// ... the rest of your code to build the chart.
?>
<form action="<?= $_SERVER['PHP_SELF']; ?>" method="get">
    <select name="range" size="1">
        <option value="+1 year">1 year</option>
        <option value="+3 months">3 months</option>
        <option value="+1 month">1 month</option>
    </select>
    <input type="submit" name="action" value="Redraw Chart">
</form>

... the reason that's the less preferred is because it causes a whole refresh of the page.

If you want to avoid a whole page refresh, you're doing pretty much the same thing, but do it with ajax. The setup is almost identical, just a couple minor changes:

// between building the data table and the javascript to build the chart...
$jsonTable = json_encode($table);
if (isset($_GET['ajax']) && $_GET['ajax']) {
    echo json_encode(array('table' => $table));
    exit;
}
// remainder of your code, then our new form from above
?>
<form id="redraw_chart_form" action="<?= $_SERVER['PHP_SELF']; ?>" data-ajaxaction="forecast.php" method="get">
    <? foreach ($_GET as $key => $val) { ?>
    <input type="hidden" name="<?= $key; ?>" value="<?= $val; ?>">
    <? } ?>
    <input type="hidden" name="ajax" id="redraw_chart_form_ajax" value="0">
    <select name="range" size="1">
        <option value="+1 year">1 year</option>
        <option value="+3 months">3 months</option>
        <option value="+1 month">1 month</option>
    </select>
    <input type="submit" name="action" value="Redraw Chart">
</form>
<script>
    // I'm assuming you've got jQuery installed, if not there are
    // endless tutorials on running your own ajax query
    $('#redraw_chart_form').submit(function(event) {
        event.preventDefault(); // this stops the form from processing normally
        $('#redraw_chart_form_ajax').val(1);
        $.ajax({
            url: $(this).attr('data-ajaxaction'),
            type: $(this).attr('method'),
            data: $(this).serialize(),
            complete: function() { $('#redraw_chart_form_ajax').val(0); },
            success: function(data) {
                // referring to the global table...
                table = data.table;
                drawChart();
            },
            error: function() {
                // left as an exercise for the reader, if ajax
                // fails, attempt to submit the form normally
                // with a full page refresh.
            }
        });
        return false; // if, for whatever reason, the preventDefault from above didn't prevent form processing, this will
    });
</script>

Edit for clarity:

  1. Don't forget to use the following block of code from the first (page refresh) example, otherwise you're not using the form at all:

    $range = isset($_GET['range'])&&$_GET['range']?$_GET['range']:'+1 year';

    $begin = new DateTime(date('Y-m-d', strtotime('+1 days')));

    $end = new DateTime(date('Y-m-d', strtotime($range)));

  2. Ajax will only work if the only data you're sending back is the json encoded block, which means your chart building data needs to be at the top of the script before any HTML output is started, including your page template. If you can't put the chart building code at the top of the script, then you'll have to add it to a whole separate script that all it does is calculate the data for the chart, and then you can have it return the ajax data without all of the other HTML on the page. If you can't do either of those things, you'll just have to turn off the Ajax bit and do a whole page refresh.


Edit 2: I added the data-ajaxaction attribute to the <form> element, this is a user defined attribute that I made up to provide a different action just for ajax. I also changed the $.ajax() call to use this attribute rather than the action attribute.

Glary answered 19/5, 2013 at 13:11 Comment(27)
Thanks. Yes, you understand me correctly, and yes I prefer the AJAX method. I have jQuery installed. I have included your code on the page, but when clicking on the 'Redraw Chart' button, I'm redirected to the url http://www.finance.nickputman.com/?ajax=0&range=%2B1+year&action=Redraw+Chart which shows me my home page, rather than the current page which has the following url: http://www.finance.nickputman.com/?page_id=174. Any ideas why this is happening?Laity
OK, first things first, in the form tag change action="<?= $_SERVER['PHP_SELF']; ?>" to action="<?= $_SERVER['PHP_SELF']; ?>?<?= $_SERVER['QUERY_STRING']; ?>". This will keep you on page_id=174, and fixing that should get the form to work, just with a page refresh. Secondly, it looks like it's not executing any of the jQuery onsubmit event. Look for errors in the javascript console that would keep the javascript from executing (if you're using Chrome, hit f12 and hit the "console" tab). Before preventDefault(), put alert('debug');, see if it runs.Glary
Thanks. The jQuery error is 'Semantic Issue - Expected token ')' on this line: url: $(this).attr('action'),. There is still an issue with the URL string for the form action. With your suggested modification I am still redirected to the home page. If I leave out <?= $_SERVER['PHP_SELF']; ?> and ? from the URL, then the URL is correct apart from the inclusion of the question mark, i.e. http://www.finance.nickputman.com/page_id=174?ajax=0&range=%2B3+months&action=Redraw+Chart.Laity
There were missing curly braces around the ajax parameters. Also, instead of adding the query string to the form "action", I added a block to create hidden inputs for them to pass through. Updated code in the edited answer. Your URL without PHP_SELF is not going to work unless you've got some strange mod_rewrite rules going on, because you need the ? before page_id=174. That's what the new hidden input block is for.Glary
Thanks, the jQuery conflict has gone now on page load, but now when I click the redraw chart button, nothing happens - i.e. the URL doesn't update. Instead, after a few seconds I get the following error in the console: format+en,default,core chart.l.js - Other Issue - Error: Table has no columns. There appears then to be an issue relating to the way your script is communicating with the Google API.Laity
Your script. I'm just helping out. I can only test so much without actual access to your page, and I didn't load it all up to check for typos and the like. Odds are it's just a simple error like that which I can't see without running the page. It's correct that it doesn't update the URL, that's how ajax works.Glary
OK, thanks. Because the page loads/works fine, and even works fine with another form & script that I am using to redraw the graph (adding starting balances to first value in the chart), and because the error only appears when clicking on the button in your form, I presumed that the issue was related to your script, rather than the existing script. I appreciate your help, but now I'm not sure what to do. I could give you access to the page and the full script as it currently stands if you like.Laity
I'm happy to look at your page if you give me access. I don't mean to suggest that the problem exists outside of the code in my answer and I don't mind trying to make sure it's 100% correct. But without full access to your codebase I'm unlikely to be more than 99% successful, ultimately it's up to you to recognize where my limited access to information means you need to tweak my answer to fit your script.Glary
OK, thanks Jason. Is there an email where I could send you a login for my site, or a way of sending you a PM via Stack Overflow?Laity
OK, thanks. Hopefully you have now received an email from me.Laity
I've updated my answer with some clarifications based on your page.Glary
Thanks for the clarification, which was helpful. I now have the form working with the page refresh method. I am still a little unclear about the ajax method, which is the one I am aiming for. I think my preferred method, of those suggested would be to have a separate script. But I'm not sure what code to put into this script and how to refer to it in the code/script running on the 'forecast' page. Any further pointers would be welcome. Thanks again.Laity
What you need to put in the separate script is everything from line 94 ($rows = array();) to line 323 (the end of the echo json_encode(array('table' => $table)); block). You also need to include anything at the beginning of the script that provides resources that you'll need to use (such as the initialization of $wpdb).Glary
@Glary your answer is missing a couple things. drawchart() is missing both arguments in your answer. You'll need to build an options object and use drawchart(table, options)Lusaka
@Rawrgulmuffins take a closer look. drawChart() is a custom function that doesn't take any arguments, just uses global data. Namely, table, which I populate right before calling drawChart().Glary
@Glary Ahh, you're saying call the draw chart which is in his question but replace data with table?Lusaka
I'm saying that in his original question, drawChart() is defined thus: function drawChart() { var data = new google.visualization.DataTable(table);... the very first thing it does is build data from the global table variable.Glary
@Glary Thanks. I now have the code you referred to in a separate script (forecast.php). I'm sorry for being slow on this one, but I'm now not sure how to use the forecast.php file. Do I remove all the code that I am putting in forecast.php from the original page and then just use include("forecast.php"); on the original page?Laity
I didn't make that very clear. forecast.php becomes the target URL for your ajax request. If that doesn't make sense, I'll update my answer in a bit to include your new fileGlary
@Glary Thanks. I have updated the code, and included the correct path to forecast.php, but for some reason the page is still refreshing. (I have also awarded the bounty as I wanted to make sure I did so in time).Laity
@Glary Sorry, I should have checked that. It's the error I was getting before: Error: Table has no columns. and line 60 of the script format+en,default,core chart.l.js is highlighted.Laity
What browser are you using? Chrome has the easiest debugging tools, when I give instructions on where to look, that's the only place they'll work. After your page has loaded, hit f12, and go to the "Network" tab. Hit "Redraw Chart". You'll see the ajax communication popup on the screen. Click it, then click the "Preview" tab. You'll see the error generated in your forecast.php script there. Fatal error: Call to a member function get_results() on a non-object in forecast.php on line 21. I know just enough about Wordpress to know that you've not started your DB connection appropriately.Glary
@Glary Thanks. As the script was running outside of a Wordpress page I needed to include wp-blog-header.php. When I did this I got an error because the array 'dates' wasn't included in forecast.php. So I included all of the code above line line 94 in the forecast.php as well. Then I got no errors, and the 'Preview' tab in Chrome is reporting that the correct data is returned, i.e. 91 rows for three months and 30 rows for 1 month, but the chart isn't redrawn on the page.Laity
It looks like your data is being sent back appropriately, which is good. For whatever reason, it's not appropriately accessing the table data to populate on the successful return. So, try this: change echo json_encode(array('table' => $table)); to echo json_encode($table); and change table = data.table; to table = data;. I've confirmed that data looks to hold the right data, but with the extra data.table dereference it's breaking, and troubleshooting is troublesome on my end.Glary
@Glary Thanks. I've made those changes, with the same result. The data is being sent back OK, but the chart isn't redrawn. I am seeing 'Uncaught error: Table has no columns' in the Console - it appears around the same time that the data is returned -i.e. it doesn't appear on page load.Laity
This is now substantially a separate issue from the one you started with, and may benefit from fresh eyes by asking a new question. For some reason, your page isn't accessing the information in the way I expect it to work. It may be a simple error on in the ajax code, but I've got a blind spot for it.Glary
@Jason. OK. I'll ask another question about it on SO and will report back. Thanks for sticking with me on this one! NickLaity

© 2022 - 2024 — McMap. All rights reserved.