docs: function () {
  		
  		var columns = this.columns,
  			xColumns = [],
  			type,
  			options = this.options,
  			series,
  			data,
  			i,
  			j,
  			r,
  			seriesIndex,
  			chartOptions,
  			allSeriesBuilders = [],
  			builder,
  			freeIndexes,
  			typeCol,
  			index;
  
  		xColumns.length = columns.length;
  		if (options.complete || options.afterComplete) {
  
  			// Get the names and shift the top row
  			for (i = 0; i < columns.length; i++) {
  				if (this.firstRowAsNames) {
  					columns[i].name = columns[i].shift();
  				}
  			}
  			
  			// Use the next columns for series
  			series = [];
  			freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders);
  
  			// Populate defined series
  			for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) {
  				builder = this.valueCount.seriesBuilders[seriesIndex];
  
  				// If the builder can be populated with remaining columns, then add it to allBuilders
  				if (builder.populateColumns(freeIndexes)) {
  					allSeriesBuilders.push(builder);
  				}
  			}
  
  			// Populate dynamic series
  			while (freeIndexes.length > 0) {
  				builder = new SeriesBuilder();
  				builder.addColumnReader(0, 'x');
  				
  				// Mark index as used (not free)
  				index = inArray(0, freeIndexes);
  				if (index !== -1) {
  					freeIndexes.splice(index, 1);
  				}
  
  				for (i = 0; i < this.valueCount.global; i++) {
  					// Create and add a column reader for the next free column index
  					builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]);
  				}
  
  				// If the builder can be populated with remaining columns, then add it to allBuilders
  				if (builder.populateColumns(freeIndexes)) {
  					allSeriesBuilders.push(builder);
  				}
  			}
  
  			// Get the data-type from the first series x column
  			if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) {
  				typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];
  				if (typeCol !== undefined) {
  					if (typeCol.isDatetime) {
  						type = 'datetime';
  					} else if (!typeCol.isNumeric) {
  						type = 'category';
  					}
  				}
  			}
  			// Axis type is category, then the "x" column should be called "name"
  			if (type === 'category') {
  				for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
  					builder = allSeriesBuilders[seriesIndex];
  					for (r = 0; r < builder.readers.length; r++) {
  						if (builder.readers[r].configName === 'x') {
  							builder.readers[r].configName = 'name';
  						}
  					}
  				}
  			}
  
  			// Read data for all builders
  			for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
  				builder = allSeriesBuilders[seriesIndex];
  
  				// Iterate down the cells of each column and add data to the series
  				data = [];
  				for (j = 0; j < columns[0].length; j++) { // TODO: which column's length should we use here
  					data[j] = builder.read(columns, j);
  				}
  
  				// Add the series
  				series[seriesIndex] = {
  					data: data
  				};
  				if (builder.name) {
  					series[seriesIndex].name = builder.name;
  				}
  				if (type === 'category') {
  					series[seriesIndex].turboThreshold = 0;
  				}
  			}
  
  
  
  			// Do the callback
  			chartOptions = {
  				series: series
  			};
  			if (type) {
  				chartOptions.xAxis = {
  					type: type
  				};
  			}
  			
  			if (options.complete) {
  				options.complete(chartOptions);
  			}
  
  			// The afterComplete hook is used internally to avoid conflict with the externally
  			// available complete option.
  			if (options.afterComplete) {
  				options.afterComplete(chartOptions);
  			}
  		}
  	}