123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- /*
- Plugin Name: amCharts Data Loader
- Description: This plugin adds external data loading capabilities to all amCharts libraries.
- Author: Martynas Majeris, amCharts
- Version: 1.0.5
- Author URI: http://www.amcharts.com/
- Copyright 2015 amCharts
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- Please note that the above license covers only this plugin. It by all means does
- not apply to any other amCharts products that are covered by different licenses.
- */
- /**
- * TODO:
- * incremental load
- * XML support (?)
- */
- /* globals AmCharts, ActiveXObject */
- /* jshint -W061 */
- /**
- * Initialize language prompt container
- */
- AmCharts.translations.dataLoader = {};
- /**
- * Set init handler
- */
- AmCharts.addInitHandler( function( chart ) {
- /**
- * Check if dataLoader is set (initialize it)
- */
- if ( undefined === chart.dataLoader || !isObject( chart.dataLoader ) )
- chart.dataLoader = {};
- /**
- * Check charts version for compatibility:
- * the first compatible version is 3.13
- */
- var version = chart.version.split( '.' );
- if ( ( Number( version[ 0 ] ) < 3 ) || ( 3 === Number( version[ 0 ] ) && ( Number( version[ 1 ] ) < 13 ) ) )
- return;
- /**
- * Define object reference for easy access
- */
- var l = chart.dataLoader;
- l.remaining = 0;
- /**
- * Set defaults
- */
- var defaults = {
- 'async': true,
- 'format': 'json',
- 'showErrors': true,
- 'showCurtain': true,
- 'noStyles': false,
- 'reload': 0,
- 'timestamp': false,
- 'delimiter': ',',
- 'skip': 0,
- 'useColumnNames': false,
- 'reverse': false,
- 'reloading': false,
- 'complete': false,
- 'error': false
- };
- /**
- * Create a function that can be used to load data (or reload via API)
- */
- l.loadData = function() {
- /**
- * Load all files in a row
- */
- if ( 'stock' === chart.type ) {
- // delay this a little bit so the chart has the chance to build itself
- setTimeout( function() {
- // preserve animation
- if ( 0 > chart.panelsSettings.startDuration ) {
- l.startDuration = chart.panelsSettings.startDuration;
- chart.panelsSettings.startDuration = 0;
- }
- // cycle through all of the data sets
- for ( var x = 0; x < chart.dataSets.length; x++ ) {
- var ds = chart.dataSets[ x ];
- // load data
- if ( undefined !== ds.dataLoader && undefined !== ds.dataLoader.url ) {
- ds.dataProvider = [];
- applyDefaults( ds.dataLoader );
- loadFile( ds.dataLoader.url, ds, ds.dataLoader, 'dataProvider' );
- }
- // load events data
- if ( undefined !== ds.eventDataLoader && undefined !== ds.eventDataLoader.url ) {
- ds.events = [];
- applyDefaults( ds.eventDataLoader );
- loadFile( ds.eventDataLoader.url, ds, ds.eventDataLoader, 'stockEvents' );
- }
- }
- }, 100 );
- } else {
- applyDefaults( l );
- if ( undefined === l.url )
- return;
- // preserve animation
- if ( undefined !== chart.startDuration && ( 0 < chart.startDuration ) ) {
- l.startDuration = chart.startDuration;
- chart.startDuration = 0;
- }
- // set empty data set
- if ( undefined === chart.dataProvider )
- chart.dataProvider = chart.type === 'map' ? {} : [];
- loadFile( l.url, chart, l, 'dataProvider' );
- }
- };
- /**
- * Trigger load
- */
- l.loadData();
- /**
- * Loads a file and determines correct parsing mechanism for it
- */
- function loadFile( url, holder, options, providerKey ) {
- // set default providerKey
- if ( undefined === providerKey )
- providerKey = 'dataProvider';
- // show curtain
- if ( options.showCurtain )
- showCurtain( undefined, options.noStyles );
- // increment loader count
- l.remaining++;
- // load the file
- AmCharts.loadFile( url, options, function( response ) {
- // error?
- if ( false === response ) {
- callFunction( options.error, options, chart );
- raiseError( AmCharts.__( 'Error loading the file', chart.language ) + ': ' + url, false, options );
- } else {
- // determine the format
- if ( undefined === options.format ) {
- // TODO
- options.format = 'json';
- }
- // lowercase
- options.format = options.format.toLowerCase();
- // invoke parsing function
- switch ( options.format ) {
- case 'json':
- holder[ providerKey ] = AmCharts.parseJSON( response );
- if ( false === holder[ providerKey ] ) {
- callFunction( options.error, options, chart );
- raiseError( AmCharts.__( 'Error parsing JSON file', chart.language ) + ': ' + l.url, false, options );
- holder[ providerKey ] = [];
- return;
- } else {
- holder[ providerKey ] = postprocess( holder[ providerKey ], options );
- callFunction( options.load, options, chart );
- }
- break;
- case 'csv':
- holder[ providerKey ] = AmCharts.parseCSV( response, options );
- if ( false === holder[ providerKey ] ) {
- callFunction( options.error, options, chart );
- raiseError( AmCharts.__( 'Error parsing CSV file', chart.language ) + ': ' + l.url, false, options );
- holder[ providerKey ] = [];
- return;
- } else {
- holder[ providerKey ] = postprocess( holder[ providerKey ], options );
- callFunction( options.load, options, chart );
- }
- break;
- default:
- callFunction( options.error, options, chart );
- raiseError( AmCharts.__( 'Unsupported data format', chart.language ) + ': ' + options.format, false, options.noStyles );
- return;
- }
- // decrement remaining counter
- l.remaining--;
- // we done?
- if ( 0 === l.remaining ) {
- // callback
- callFunction( options.complete, chart );
- // take in the new data
- if ( options.async ) {
- if ( 'map' === chart.type )
- chart.validateNow( true );
- else {
- // take in new data
- chart.validateData();
- // make the chart animate again
- if ( l.startDuration ) {
- if ( 'stock' === chart.type ) {
- chart.panelsSettings.startDuration = l.startDuration;
- for ( var x = 0; x < chart.panels.length; x++ ) {
- chart.panels[ x ].startDuration = l.startDuration;
- chart.panels[ x ].animateAgain();
- }
- } else {
- chart.startDuration = l.startDuration;
- chart.animateAgain();
- }
- }
- }
- }
- // restore default period
- if ( 'stock' === chart.type && !options.reloading && undefined !== chart.periodSelector )
- chart.periodSelector.setDefaultPeriod();
- // remove curtain
- removeCurtain();
- }
- // schedule another load if necessary
- if ( options.reload ) {
- if ( options.timeout )
- clearTimeout( options.timeout );
- options.timeout = setTimeout( loadFile, 1000 * options.reload, url, holder, options );
- options.reloading = true;
- }
- }
- } );
- }
- /**
- * Checks if postProcess is set and invokes the handler
- */
- function postprocess( data, options ) {
- if ( undefined !== options.postProcess && isFunction( options.postProcess ) )
- try {
- return options.postProcess.call( this, data, options );
- } catch ( e ) {
- raiseError( AmCharts.__( 'Error loading file', chart.language ) + ': ' + options.url, false, options );
- return data;
- } else
- return data;
- }
- /**
- * Returns true if argument is array
- */
- function isObject( obj ) {
- return 'object' === typeof( obj );
- }
- /**
- * Returns true is argument is a function
- */
- function isFunction( obj ) {
- return 'function' === typeof( obj );
- }
- /**
- * Applies defaults to config object
- */
- function applyDefaults( obj ) {
- for ( var x in defaults ) {
- if ( defaults.hasOwnProperty( x ) )
- setDefault( obj, x, defaults[ x ] );
- }
- }
- /**
- * Checks if object property is set, sets with a default if it isn't
- */
- function setDefault( obj, key, value ) {
- if ( undefined === obj[ key ] )
- obj[ key ] = value;
- }
- /**
- * Raises an internal error (writes it out to console)
- */
- function raiseError( msg, error, options ) {
- if ( options.showErrors )
- showCurtain( msg, options.noStyles );
- else {
- removeCurtain();
- console.log( msg );
- }
- }
- /**
- * Shows curtain over chart area
- */
- function showCurtain( msg, noStyles ) {
- // remove previous curtain if there is one
- removeCurtain();
- // did we pass in the message?
- if ( undefined === msg )
- msg = AmCharts.__( 'Loading data...', chart.language );
- // create and populate curtain element
- var curtain = document.createElement( 'div' );
- curtain.setAttribute( 'id', chart.div.id + '-curtain' );
- curtain.className = 'amcharts-dataloader-curtain';
- if ( true !== noStyles ) {
- curtain.style.position = 'absolute';
- curtain.style.top = 0;
- curtain.style.left = 0;
- curtain.style.width = ( undefined !== chart.realWidth ? chart.realWidth : chart.divRealWidth ) + 'px';
- curtain.style.height = ( undefined !== chart.realHeight ? chart.realHeight : chart.divRealHeight ) + 'px';
- curtain.style.textAlign = 'center';
- curtain.style.display = 'table';
- curtain.style.fontSize = '20px';
- try {
- curtain.style.background = 'rgba(255, 255, 255, 0.3)';
- }
- catch ( e ) {
- curtain.style.background = 'rgb(255, 255, 255)';
- }
- curtain.innerHTML = '<div style="display: table-cell; vertical-align: middle;">' + msg + '</div>';
- } else {
- curtain.innerHTML = msg;
- }
- chart.containerDiv.appendChild( curtain );
- l.curtain = curtain;
- }
- /**
- * Removes the curtain
- */
- function removeCurtain() {
- try {
- if ( undefined !== l.curtain )
- chart.containerDiv.removeChild( l.curtain );
- } catch ( e ) {
- // do nothing
- }
- l.curtain = undefined;
- }
- /**
- * Execute callback function
- */
- function callFunction( func, param1, param2, param3 ) {
- if ( 'function' === typeof func )
- func.call( l, param1, param2, param3 );
- }
- }, [ 'pie', 'serial', 'xy', 'funnel', 'radar', 'gauge', 'gantt', 'stock', 'map' ] );
- /**
- * Returns prompt in a chart language (set by chart.language) if it is
- * available
- */
- if ( undefined === AmCharts.__ ) {
- AmCharts.__ = function( msg, language ) {
- if ( undefined !== language && undefined !== AmCharts.translations.dataLoader[ language ] && undefined !== AmCharts.translations.dataLoader[ language ][ msg ] )
- return AmCharts.translations.dataLoader[ language ][ msg ];
- else
- return msg;
- };
- }
- /**
- * Loads a file from url and calls function handler with the result
- */
- AmCharts.loadFile = function( url, options, handler ) {
- // create the request
- var request;
- if ( window.XMLHttpRequest ) {
- // IE7+, Firefox, Chrome, Opera, Safari
- request = new XMLHttpRequest();
- } else {
- // code for IE6, IE5
- request = new ActiveXObject( 'Microsoft.XMLHTTP' );
- }
- // set handler for data if async loading
- request.onreadystatechange = function() {
- if ( 4 === request.readyState && 404 === request.status )
- handler.call( this, false );
- else if ( 4 === request.readyState && 200 === request.status )
- handler.call( this, request.responseText );
- };
- // load the file
- try {
- request.open( 'GET', options.timestamp ? AmCharts.timestampUrl( url ) : url, options.async );
- request.send();
- } catch ( e ) {
- handler.call( this, false );
- }
- };
- /**
- * Parses JSON string into an object
- */
- AmCharts.parseJSON = function( response ) {
- try {
- if ( undefined !== JSON )
- return JSON.parse( response );
- else
- return eval( response );
- } catch ( e ) {
- return false;
- }
- };
- /**
- * Prases CSV string into an object
- */
- AmCharts.parseCSV = function( response, options ) {
- // parse CSV into array
- var data = AmCharts.CSVToArray( response, options.delimiter );
- // init resuling array
- var res = [];
- var cols = [];
- var col, i;
- // first row holds column names?
- if ( options.useColumnNames ) {
- cols = data.shift();
- // normalize column names
- for ( var x = 0; x < cols.length; x++ ) {
- // trim
- col = cols[ x ].replace( /^\s+|\s+$/gm, '' );
- // check for empty
- if ( '' === col )
- col = 'col' + x;
- cols[ x ] = col;
- }
- if ( 0 < options.skip )
- options.skip--;
- }
- // skip rows
- for ( i = 0; i < options.skip; i++ )
- data.shift();
- // iterate through the result set
- var row;
- while ( ( row = options.reverse ? data.pop() : data.shift() ) ) {
- var dataPoint = {};
- for ( i = 0; i < row.length; i++ ) {
- col = undefined === cols[ i ] ? 'col' + i : cols[ i ];
- dataPoint[ col ] = row[ i ];
- }
- res.push( dataPoint );
- }
- return res;
- };
- /**
- * Parses CSV data into array
- * Taken from here: (thanks!)
- * http://www.bennadel.com/blog/1504-ask-ben-parsing-csv-strings-with-javascript-exec-regular-expression-command.htm
- */
- AmCharts.CSVToArray = function( strData, strDelimiter ) {
- // Check to see if the delimiter is defined. If not,
- // then default to comma.
- strDelimiter = ( strDelimiter || "," );
- // Create a regular expression to parse the CSV values.
- var objPattern = new RegExp(
- (
- // Delimiters.
- "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
- // Quoted fields.
- "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
- // Standard fields.
- "([^\"\\" + strDelimiter + "\\r\\n]*))"
- ),
- "gi"
- );
- // Create an array to hold our data. Give the array
- // a default empty first row.
- var arrData = [
- []
- ];
- // Create an array to hold our individual pattern
- // matching groups.
- var arrMatches = null;
- // Keep looping over the regular expression matches
- // until we can no longer find a match.
- while ( ( arrMatches = objPattern.exec( strData ) ) ) {
- // Get the delimiter that was found.
- var strMatchedDelimiter = arrMatches[ 1 ];
- // Check to see if the given delimiter has a length
- // (is not the start of string) and if it matches
- // field delimiter. If id does not, then we know
- // that this delimiter is a row delimiter.
- if (
- strMatchedDelimiter.length &&
- ( strMatchedDelimiter !== strDelimiter )
- ) {
- // Since we have reached a new row of data,
- // add an empty row to our data array.
- arrData.push( [] );
- }
- // Now that we have our delimiter out of the way,
- // let's check to see which kind of value we
- // captured (quoted or unquoted).
- var strMatchedValue;
- if ( arrMatches[ 2 ] ) {
- // We found a quoted value. When we capture
- // this value, unescape any double quotes.
- strMatchedValue = arrMatches[ 2 ].replace(
- new RegExp( "\"\"", "g" ),
- "\""
- );
- } else {
- // We found a non-quoted value.
- strMatchedValue = arrMatches[ 3 ];
- }
- // Now that we have our value string, let's add
- // it to the data array.
- arrData[ arrData.length - 1 ].push( strMatchedValue );
- }
- // Return the parsed data.
- return ( arrData );
- };
- /**
- * Appends timestamp to the url
- */
- AmCharts.timestampUrl = function( url ) {
- var p = url.split( '?' );
- if ( 1 === p.length )
- p[ 1 ] = new Date().getTime();
- else
- p[ 1 ] += '&' + new Date().getTime();
- return p.join( '?' );
- };
|