Subversion Repositories php-qbpwcf

Rev

Rev 23 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
23 liveuser 1
/*
2
Plugin Name: amCharts Data Loader
3
Description: This plugin adds external data loading capabilities to all amCharts libraries.
4
Author: Martynas Majeris, amCharts
5
Version: 1.0.12
6
Author URI: http://www.amcharts.com/
7
 
8
Copyright 2015 amCharts
9
 
10
Licensed under the Apache License, Version 2.0 (the "License");
11
you may not use this file except in compliance with the License.
12
You may obtain a copy of the License at
13
 
14
  http://www.apache.org/licenses/LICENSE-2.0
15
 
16
Unless required by applicable law or agreed to in writing, software
17
distributed under the License is distributed on an "AS IS" BASIS,
18
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
See the License for the specific language governing permissions and
20
limitations under the License.
21
 
22
Please note that the above license covers only this plugin. It by all means does
23
not apply to any other amCharts products that are covered by different licenses.
24
*/
25
 
26
/**
27
 * TODO:
28
 * incremental load
29
 * XML support (?)
30
 */
31
 
32
/* globals AmCharts, ActiveXObject */
33
/* jshint -W061 */
34
 
35
/**
36
 * Initialize language prompt container
37
 */
38
AmCharts.translations.dataLoader = {};
39
 
40
/**
41
 * Set init handler
42
 */
43
AmCharts.addInitHandler( function( chart ) {
44
 
45
  /**
46
   * Check if dataLoader is set (initialize it)
47
   */
48
  if ( undefined === chart.dataLoader || !isObject( chart.dataLoader ) )
49
    chart.dataLoader = {};
50
 
51
  /**
52
   * Check charts version for compatibility:
53
   * the first compatible version is 3.13
54
   */
55
  var version = chart.version.split( '.' );
56
  if ( ( Number( version[ 0 ] ) < 3 ) || ( 3 === Number( version[ 0 ] ) && ( Number( version[ 1 ] ) < 13 ) ) )
57
    return;
58
 
59
  /**
60
   * Define object reference for easy access
61
   */
62
  var l = chart.dataLoader;
63
  l.remaining = 0;
64
 
65
  /**
66
   * Set defaults
67
   */
68
  var defaults = {
69
    'async': true,
70
    'format': 'json',
71
    'showErrors': true,
72
    'showCurtain': true,
73
    'noStyles': false,
74
    'reload': 0,
75
    'timestamp': false,
76
    'delimiter': ',',
77
    'skip': 0,
78
    'skipEmpty': true,
79
    'useColumnNames': false,
80
    'reverse': false,
81
    'reloading': false,
82
    'complete': false,
83
    'error': false,
84
    'headers': [],
85
    'chart': chart
86
  };
87
 
88
  /**
89
   * Create a function that can be used to load data (or reload via API)
90
   */
91
  l.loadData = function() {
92
 
93
    /**
94
     * Load all files in a row
95
     */
96
    if ( 'stock' === chart.type ) {
97
 
98
      // delay this a little bit so the chart has the chance to build itself
99
      setTimeout( function() {
100
 
101
        // preserve animation
102
        if ( 0 > chart.panelsSettings.startDuration ) {
103
          l.startDuration = chart.panelsSettings.startDuration;
104
          chart.panelsSettings.startDuration = 0;
105
        }
106
 
107
        // cycle through all of the data sets
108
        for ( var x = 0; x < chart.dataSets.length; x++ ) {
109
          var ds = chart.dataSets[ x ];
110
 
111
          // load data
112
          if ( undefined !== ds.dataLoader && undefined !== ds.dataLoader.url ) {
113
 
114
            ds.dataProvider = [];
115
            applyDefaults( ds.dataLoader );
116
            loadFile( ds.dataLoader.url, ds, ds.dataLoader, 'dataProvider' );
117
 
118
          }
119
 
120
          // load events data
121
          if ( undefined !== ds.eventDataLoader && undefined !== ds.eventDataLoader.url ) {
122
 
123
            ds.events = [];
124
            applyDefaults( ds.eventDataLoader );
125
            loadFile( ds.eventDataLoader.url, ds, ds.eventDataLoader, 'stockEvents' );
126
 
127
          }
128
        }
129
 
130
      }, 100 );
131
 
132
    } else {
133
 
134
      applyDefaults( l );
135
 
136
      if ( undefined === l.url )
137
        return;
138
 
139
      // preserve animation
140
      if ( undefined !== chart.startDuration && ( 0 < chart.startDuration ) ) {
141
        l.startDuration = chart.startDuration;
142
        chart.startDuration = 0;
143
      }
144
 
145
      if ( 'gauge' === chart.type ) {
146
        // set empty data set
147
        if ( undefined === chart.arrows )
148
          chart.arrows = [];
149
 
150
        loadFile( l.url, chart, l, 'arrows' );
151
      } else {
152
        // set empty data set
153
        if ( undefined === chart.dataProvider )
154
          chart.dataProvider = chart.type === 'map' ? {} : [];
155
 
156
        loadFile( l.url, chart, l, 'dataProvider' );
157
      }
158
 
159
    }
160
 
161
  };
162
 
163
  /**
164
   * Trigger load
165
   */
166
  l.loadData();
167
 
168
  /**
169
   * Loads a file and determines correct parsing mechanism for it
170
   */
171
  function loadFile( url, holder, options, providerKey ) {
172
 
173
    // set default providerKey
174
    if ( undefined === providerKey )
175
      providerKey = 'dataProvider';
176
 
177
    // show curtain
178
    if ( options.showCurtain )
179
      showCurtain( undefined, options.noStyles );
180
 
181
    // increment loader count
182
    l.remaining++;
183
 
184
    // load the file
185
    AmCharts.loadFile( url, options, function( response ) {
186
 
187
      // error?
188
      if ( false === response ) {
189
        callFunction( options.error, options, chart );
190
        raiseError( AmCharts.__( 'Error loading the file', chart.language ) + ': ' + url, false, options );
191
      } else {
192
 
193
        // determine the format
194
        if ( undefined === options.format ) {
195
          // TODO
196
          options.format = 'json';
197
        }
198
 
199
        // lowercase
200
        options.format = options.format.toLowerCase();
201
 
202
        // invoke parsing function
203
        switch ( options.format ) {
204
 
205
          case 'json':
206
 
207
            holder[ providerKey ] = AmCharts.parseJSON( response );
208
 
209
            if ( false === holder[ providerKey ] ) {
210
              callFunction( options.error, options, chart );
211
              raiseError( AmCharts.__( 'Error parsing JSON file', chart.language ) + ': ' + l.url, false, options );
212
              holder[ providerKey ] = [];
213
              return;
214
            } else {
215
              holder[ providerKey ] = postprocess( holder[ providerKey ], options );
216
              callFunction( options.load, options, chart );
217
            }
218
 
219
            break;
220
 
221
          case 'csv':
222
 
223
            holder[ providerKey ] = AmCharts.parseCSV( response, options );
224
 
225
            if ( false === holder[ providerKey ] ) {
226
              callFunction( options.error, options, chart );
227
              raiseError( AmCharts.__( 'Error parsing CSV file', chart.language ) + ': ' + l.url, false, options );
228
              holder[ providerKey ] = [];
229
              return;
230
            } else {
231
              holder[ providerKey ] = postprocess( holder[ providerKey ], options );
232
              callFunction( options.load, options, chart );
233
            }
234
 
235
            break;
236
 
237
          default:
238
            callFunction( options.error, options, chart );
239
            raiseError( AmCharts.__( 'Unsupported data format', chart.language ) + ': ' + options.format, false, options.noStyles );
240
            return;
241
        }
242
 
243
        // decrement remaining counter
244
        l.remaining--;
245
 
246
        // we done?
247
        if ( 0 === l.remaining ) {
248
 
249
          // callback
250
          callFunction( options.complete, chart );
251
 
252
          // take in the new data
253
          if ( options.async ) {
254
 
255
            if ( 'map' === chart.type ) {
256
 
257
              // take in new data
258
              chart.validateNow( true );
259
 
260
              // remove curtain
261
              removeCurtain();
262
 
263
            } else {
264
 
265
              // add a dataUpdated event to handle post-load stuff
266
              if ( 'gauge' !== chart.type ) {
267
                chart.addListener( "dataUpdated", function( event ) {
268
 
269
                  // restore default period (stock chart)
270
                  if ( 'stock' === chart.type && !options.reloading && undefined !== chart.periodSelector ) {
271
                    chart.periodSelector.setDefaultPeriod();
272
                  }
273
 
274
                  // remove curtain
275
                  removeCurtain();
276
 
277
                  // remove this listener
278
                  chart.events.dataUpdated.pop();
279
                } );
280
              }
281
 
282
 
283
              // take in new data
284
              chart.validateData();
285
 
286
              // invalidate size for the pie chart
287
              // disabled for now as it is not longer necessary
288
              /*if ( 'pie' === chart.type && chart.invalidateSize !== undefined )
289
                chart.invalidateSize();*/
290
 
291
              // gauge chart does not trigger dataUpdated event
292
              // let's explicitly remove the curtain for it
293
              if ( 'gauge' === chart.type )
294
                removeCurtain();
295
 
296
              // make the chart animate again
297
              if ( l.startDuration ) {
298
                if ( 'stock' === chart.type ) {
299
                  chart.panelsSettings.startDuration = l.startDuration;
300
                  for ( var x = 0; x < chart.panels.length; x++ ) {
301
                    chart.panels[ x ].startDuration = l.startDuration;
302
                    chart.panels[ x ].animateAgain();
303
                  }
304
                } else {
305
                  chart.startDuration = l.startDuration;
306
                  if ( chart.animateAgain !== undefined )
307
                    chart.animateAgain();
308
                }
309
              }
310
            }
311
          }
312
 
313
        }
314
 
315
        // schedule another load if necessary
316
        if ( options.reload ) {
317
 
318
          if ( options.timeout )
319
            clearTimeout( options.timeout );
320
 
321
          options.timeout = setTimeout( loadFile, 1000 * options.reload, url, holder, options );
322
          options.reloading = true;
323
 
324
        }
325
 
326
      }
327
 
328
    } );
329
 
330
  }
331
 
332
  /**
333
   * Checks if postProcess is set and invokes the handler
334
   */
335
  function postprocess( data, options ) {
336
    if ( undefined !== options.postProcess && isFunction( options.postProcess ) )
337
      try {
338
        return options.postProcess.call( l, data, options, chart );
339
      } catch ( e ) {
340
        raiseError( AmCharts.__( 'Error loading file', chart.language ) + ': ' + options.url, false, options );
341
        return data;
342
      } else
343
        return data;
344
  }
345
 
346
  /**
347
   * Returns true if argument is array
348
   */
349
  function isObject( obj ) {
350
    return 'object' === typeof( obj );
351
  }
352
 
353
  /**
354
   * Returns true is argument is a function
355
   */
356
  function isFunction( obj ) {
357
    return 'function' === typeof( obj );
358
  }
359
 
360
  /**
361
   * Applies defaults to config object
362
   */
363
  function applyDefaults( obj ) {
364
    for ( var x in defaults ) {
365
      if ( defaults.hasOwnProperty( x ) )
366
        setDefault( obj, x, defaults[ x ] );
367
    }
368
  }
369
 
370
  /**
371
   * Checks if object property is set, sets with a default if it isn't
372
   */
373
  function setDefault( obj, key, value ) {
374
    if ( undefined === obj[ key ] )
375
      obj[ key ] = value;
376
  }
377
 
378
  /**
379
   * Raises an internal error (writes it out to console)
380
   */
381
  function raiseError( msg, error, options ) {
382
 
383
    if ( options.showErrors )
384
      showCurtain( msg, options.noStyles );
385
    else {
386
      removeCurtain();
387
      console.log( msg );
388
    }
389
 
390
  }
391
 
392
  /**
393
   * Shows curtain over chart area
394
   */
395
  function showCurtain( msg, noStyles ) {
396
 
397
    // remove previous curtain if there is one
398
    removeCurtain();
399
 
400
    // did we pass in the message?
401
    if ( undefined === msg )
402
      msg = AmCharts.__( 'Loading data...', chart.language );
403
 
404
    // create and populate curtain element
405
    var curtain = document.createElement( 'div' );
406
    curtain.setAttribute( 'id', chart.div.id + '-curtain' );
407
    curtain.className = 'amcharts-dataloader-curtain';
408
 
409
    if ( true !== noStyles ) {
410
      curtain.style.position = 'absolute';
411
      curtain.style.top = 0;
412
      curtain.style.left = 0;
413
      curtain.style.width = ( undefined !== chart.realWidth ? chart.realWidth : chart.divRealWidth ) + 'px';
414
      curtain.style.height = ( undefined !== chart.realHeight ? chart.realHeight : chart.divRealHeight ) + 'px';
415
      curtain.style.textAlign = 'center';
416
      curtain.style.display = 'table';
417
      curtain.style.fontSize = '20px';
418
      try {
419
        curtain.style.background = 'rgba(255, 255, 255, 0.3)';
420
      } catch ( e ) {
421
        curtain.style.background = 'rgb(255, 255, 255)';
422
      }
423
      curtain.innerHTML = '<div style="display: table-cell; vertical-align: middle;">' + msg + '</div>';
424
    } else {
425
      curtain.innerHTML = msg;
426
    }
427
    chart.containerDiv.appendChild( curtain );
428
 
429
    l.curtain = curtain;
430
  }
431
 
432
  /**
433
   * Removes the curtain
434
   */
435
  function removeCurtain() {
436
    try {
437
      if ( undefined !== l.curtain )
438
        chart.containerDiv.removeChild( l.curtain );
439
    } catch ( e ) {
440
      // do nothing
441
    }
442
 
443
    l.curtain = undefined;
444
 
445
  }
446
 
447
  /**
448
   * Execute callback function
449
   */
450
  function callFunction( func, param1, param2, param3 ) {
451
    if ( 'function' === typeof func )
452
      func.call( l, param1, param2, param3 );
453
  }
454
 
455
}, [ 'pie', 'serial', 'xy', 'funnel', 'radar', 'gauge', 'gantt', 'stock', 'map' ] );
456
 
457
 
458
/**
459
 * Returns prompt in a chart language (set by chart.language) if it is
460
 * available
461
 */
462
if ( undefined === AmCharts.__ ) {
463
  AmCharts.__ = function( msg, language ) {
464
    if ( undefined !== language && undefined !== AmCharts.translations.dataLoader[ language ] && undefined !== AmCharts.translations.dataLoader[ language ][ msg ] )
465
      return AmCharts.translations.dataLoader[ language ][ msg ];
466
    else
467
      return msg;
468
  };
469
}
470
 
471
/**
472
 * Loads a file from url and calls function handler with the result
473
 */
474
AmCharts.loadFile = function( url, options, handler ) {
475
 
476
  // prepopulate options with minimal defaults if necessary
477
  if ( typeof( options ) !== "object" )
478
    options = {};
479
  if ( options.async === undefined )
480
    options.async = true;
481
 
482
  // create the request
483
  var request;
484
  if ( window.XMLHttpRequest ) {
485
    // IE7+, Firefox, Chrome, Opera, Safari
486
    request = new XMLHttpRequest();
487
  } else {
488
    // code for IE6, IE5
489
    request = new ActiveXObject( 'Microsoft.XMLHTTP' );
490
  }
491
 
492
  // open the connection
493
  try {
494
    request.open( 'GET', options.timestamp ? AmCharts.timestampUrl( url ) : url, options.async );
495
  } catch ( e ) {
496
    handler.call( this, false );
497
  }
498
 
499
  // add headers?
500
  if ( options.headers !== undefined && options.headers.length ) {
501
    for ( var i = 0; i < options.headers.length; i++ ) {
502
      var header = options.headers[ i ];
503
      request.setRequestHeader( header.key, header.value );
504
    }
505
  }
506
 
507
  // set handler for data if async loading
508
  request.onreadystatechange = function() {
509
 
510
    if ( 4 === request.readyState && 404 === request.status )
511
      handler.call( this, false );
512
 
513
    else if ( 4 === request.readyState && 200 === request.status )
514
      handler.call( this, request.responseText );
515
 
516
  };
517
 
518
  // load the file
519
  try {
520
    request.send();
521
  } catch ( e ) {
522
    handler.call( this, false );
523
  }
524
 
525
};
526
 
527
/**
528
 * Parses JSON string into an object
529
 */
530
AmCharts.parseJSON = function( response ) {
531
  try {
532
    if ( undefined !== JSON )
533
      return JSON.parse( response );
534
    else
535
      return eval( response );
536
  } catch ( e ) {
537
    return false;
538
  }
539
};
540
 
541
/**
542
 * Prases CSV string into an object
543
 */
544
AmCharts.parseCSV = function( response, options ) {
545
 
546
  // parse CSV into array
547
  var data = AmCharts.CSVToArray( response, options.delimiter );
548
 
549
  // init resuling array
550
  var res = [];
551
  var cols = [];
552
  var col, i;
553
 
554
  // first row holds column names?
555
  if ( options.useColumnNames ) {
556
    cols = data.shift();
557
 
558
    // normalize column names
559
    for ( var x = 0; x < cols.length; x++ ) {
560
      // trim
561
      col = cols[ x ].replace( /^\s+|\s+$/gm, '' );
562
 
563
      // check for empty
564
      if ( '' === col )
565
        col = 'col' + x;
566
 
567
      cols[ x ] = col;
568
    }
569
 
570
    if ( 0 < options.skip )
571
      options.skip--;
572
  }
573
 
574
  // skip rows
575
  for ( i = 0; i < options.skip; i++ )
576
    data.shift();
577
 
578
  // iterate through the result set
579
  var row;
580
  while ( ( row = options.reverse ? data.pop() : data.shift() ) ) {
581
    if ( options.skipEmpty && row.length === 1 && row[ 0 ] === '' )
582
      continue;
583
    var dataPoint = {};
584
    for ( i = 0; i < row.length; i++ ) {
585
      col = undefined === cols[ i ] ? 'col' + i : cols[ i ];
586
      dataPoint[ col ] = row[ i ];
587
    }
588
    res.push( dataPoint );
589
  }
590
 
591
  return res;
592
};
593
 
594
/**
595
 * Parses CSV data into array
596
 * Taken from here: (thanks!)
597
 * http://www.bennadel.com/blog/1504-ask-ben-parsing-csv-strings-with-javascript-exec-regular-expression-command.htm
598
 */
599
AmCharts.CSVToArray = function( strData, strDelimiter ) {
600
  // Check to see if the delimiter is defined. If not,
601
  // then default to comma.
602
  strDelimiter = ( strDelimiter || "," );
603
 
604
  // Create a regular expression to parse the CSV values.
605
  var objPattern = new RegExp(
606
    (
607
      // Delimiters.
608
      "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
609
 
610
      // Quoted fields.
611
      "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
612
 
613
      // Standard fields.
614
      "([^\"\\" + strDelimiter + "\\r\\n]*))"
615
    ),
616
    "gi"
617
  );
618
 
619
 
620
  // Create an array to hold our data. Give the array
621
  // a default empty first row.
622
  var arrData = [
623
    []
624
  ];
625
 
626
  // Create an array to hold our individual pattern
627
  // matching groups.
628
  var arrMatches = null;
629
 
630
 
631
  // Keep looping over the regular expression matches
632
  // until we can no longer find a match.
633
  while ( ( arrMatches = objPattern.exec( strData ) ) ) {
634
 
635
    // Get the delimiter that was found.
636
    var strMatchedDelimiter = arrMatches[ 1 ];
637
 
638
    // Check to see if the given delimiter has a length
639
    // (is not the start of string) and if it matches
640
    // field delimiter. If id does not, then we know
641
    // that this delimiter is a row delimiter.
642
    if (
643
      strMatchedDelimiter.length &&
644
      ( strMatchedDelimiter !== strDelimiter )
645
    ) {
646
 
647
      // Since we have reached a new row of data,
648
      // add an empty row to our data array.
649
      arrData.push( [] );
650
 
651
    }
652
 
653
 
654
    // Now that we have our delimiter out of the way,
655
    // let's check to see which kind of value we
656
    // captured (quoted or unquoted).
657
    var strMatchedValue;
658
    if ( arrMatches[ 2 ] ) {
659
 
660
      // We found a quoted value. When we capture
661
      // this value, unescape any double quotes.
662
      strMatchedValue = arrMatches[ 2 ].replace(
663
        new RegExp( "\"\"", "g" ),
664
        "\""
665
      );
666
 
667
    } else {
668
 
669
      // We found a non-quoted value.
670
      strMatchedValue = arrMatches[ 3 ];
671
 
672
    }
673
 
674
 
675
    // Now that we have our value string, let's add
676
    // it to the data array.
677
    arrData[ arrData.length - 1 ].push( strMatchedValue );
678
  }
679
 
680
  // Return the parsed data.
681
  return ( arrData );
682
};
683
 
684
/**
685
 * Appends timestamp to the url
686
 */
687
AmCharts.timestampUrl = function( url ) {
688
  var p = url.split( '?' );
689
  if ( 1 === p.length )
690
    p[ 1 ] = new Date().getTime();
691
  else
692
    p[ 1 ] += '&' + new Date().getTime();
693
  return p.join( '?' );
694
};