Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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