/base/000_base/node_modules/highcharts/js/modules/data.src.js |
@@ -0,0 +1,981 @@ |
/** |
* @license Highcharts JS v5.0.12 (2017-05-24) |
* Data module |
* |
* (c) 2012-2017 Torstein Honsi |
* |
* License: www.highcharts.com/license |
*/ |
'use strict'; |
(function(factory) { |
if (typeof module === 'object' && module.exports) { |
module.exports = factory; |
} else { |
factory(Highcharts); |
} |
}(function(Highcharts) { |
(function(Highcharts) { |
/** |
* Data module |
* |
* (c) 2012-2017 Torstein Honsi |
* |
* License: www.highcharts.com/license |
*/ |
|
/* global jQuery */ |
|
// Utilities |
var win = Highcharts.win, |
doc = win.document, |
each = Highcharts.each, |
objectEach = Highcharts.objectEach, |
pick = Highcharts.pick, |
inArray = Highcharts.inArray, |
isNumber = Highcharts.isNumber, |
splat = Highcharts.splat, |
SeriesBuilder; |
|
|
// The Data constructor |
var Data = function(dataOptions, chartOptions) { |
this.init(dataOptions, chartOptions); |
}; |
|
// Set the prototype properties |
Highcharts.extend(Data.prototype, { |
|
/** |
* Initialize the Data object with the given options |
*/ |
init: function(options, chartOptions) { |
this.options = options; |
this.chartOptions = chartOptions; |
this.columns = options.columns || this.rowsToColumns(options.rows) || []; |
this.firstRowAsNames = pick(options.firstRowAsNames, true); |
this.decimalRegex = options.decimalPoint && new RegExp('^(-?[0-9]+)' + options.decimalPoint + '([0-9]+)$'); |
|
// This is a two-dimensional array holding the raw, trimmed string values |
// with the same organisation as the columns array. It makes it possible |
// for example to revert from interpreted timestamps to string-based |
// categories. |
this.rawColumns = []; |
|
// No need to parse or interpret anything |
if (this.columns.length) { |
this.dataFound(); |
|
// Parse and interpret |
} else { |
|
// Parse a CSV string if options.csv is given |
this.parseCSV(); |
|
// Parse a HTML table if options.table is given |
this.parseTable(); |
|
// Parse a Google Spreadsheet |
this.parseGoogleSpreadsheet(); |
} |
|
}, |
|
/** |
* Get the column distribution. For example, a line series takes a single column for |
* Y values. A range series takes two columns for low and high values respectively, |
* and an OHLC series takes four columns. |
*/ |
getColumnDistribution: function() { |
var chartOptions = this.chartOptions, |
options = this.options, |
xColumns = [], |
getValueCount = function(type) { |
return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length; |
}, |
getPointArrayMap = function(type) { |
return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap; |
}, |
globalType = chartOptions && chartOptions.chart && chartOptions.chart.type, |
individualCounts = [], |
seriesBuilders = [], |
seriesIndex = 0, |
i; |
|
each((chartOptions && chartOptions.series) || [], function(series) { |
individualCounts.push(getValueCount(series.type || globalType)); |
}); |
|
// Collect the x-column indexes from seriesMapping |
each((options && options.seriesMapping) || [], function(mapping) { |
xColumns.push(mapping.x || 0); |
}); |
|
// If there are no defined series with x-columns, use the first column as x column |
if (xColumns.length === 0) { |
xColumns.push(0); |
} |
|
// Loop all seriesMappings and constructs SeriesBuilders from |
// the mapping options. |
each((options && options.seriesMapping) || [], function(mapping) { |
var builder = new SeriesBuilder(), |
numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType), |
seriesArr = (chartOptions && chartOptions.series) || [], |
series = seriesArr[seriesIndex] || {}, |
pointArrayMap = getPointArrayMap(series.type || globalType) || ['y']; |
|
// Add an x reader from the x property or from an undefined column |
// if the property is not set. It will then be auto populated later. |
builder.addColumnReader(mapping.x, 'x'); |
|
// Add all column mappings |
objectEach(mapping, function(val, name) { |
if (name !== 'x') { |
builder.addColumnReader(val, name); |
} |
}); |
|
// Add missing columns |
for (i = 0; i < numberOfValueColumnsNeeded; i++) { |
if (!builder.hasReader(pointArrayMap[i])) { |
//builder.addNextColumnReader(pointArrayMap[i]); |
// Create and add a column reader for the next free column index |
builder.addColumnReader(undefined, pointArrayMap[i]); |
} |
} |
|
seriesBuilders.push(builder); |
seriesIndex++; |
}); |
|
var globalPointArrayMap = getPointArrayMap(globalType); |
if (globalPointArrayMap === undefined) { |
globalPointArrayMap = ['y']; |
} |
|
this.valueCount = { |
global: getValueCount(globalType), |
xColumns: xColumns, |
individual: individualCounts, |
seriesBuilders: seriesBuilders, |
globalPointArrayMap: globalPointArrayMap |
}; |
}, |
|
/** |
* When the data is parsed into columns, either by CSV, table, GS or direct input, |
* continue with other operations. |
*/ |
dataFound: function() { |
|
if (this.options.switchRowsAndColumns) { |
this.columns = this.rowsToColumns(this.columns); |
} |
|
// Interpret the info about series and columns |
this.getColumnDistribution(); |
|
// Interpret the values into right types |
this.parseTypes(); |
|
// Handle columns if a handleColumns callback is given |
if (this.parsed() !== false) { |
|
// Complete if a complete callback is given |
this.complete(); |
} |
|
}, |
|
/** |
* Parse a CSV input string |
*/ |
parseCSV: function() { |
var self = this, |
options = this.options, |
csv = options.csv, |
columns = this.columns, |
startRow = options.startRow || 0, |
endRow = options.endRow || Number.MAX_VALUE, |
startColumn = options.startColumn || 0, |
endColumn = options.endColumn || Number.MAX_VALUE, |
itemDelimiter, |
lines, |
activeRowNo = 0; |
|
if (csv) { |
|
lines = csv |
.replace(/\r\n/g, '\n') // Unix |
.replace(/\r/g, '\n') // Mac |
.split(options.lineDelimiter || '\n'); |
|
itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ','); |
|
each(lines, function(line, rowNo) { |
var trimmed = self.trim(line), |
isComment = trimmed.indexOf('#') === 0, |
isBlank = trimmed === '', |
items; |
|
if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) { |
items = line.split(itemDelimiter); |
each(items, function(item, colNo) { |
if (colNo >= startColumn && colNo <= endColumn) { |
if (!columns[colNo - startColumn]) { |
columns[colNo - startColumn] = []; |
} |
|
columns[colNo - startColumn][activeRowNo] = item; |
} |
}); |
activeRowNo += 1; |
} |
}); |
|
this.dataFound(); |
} |
}, |
|
/** |
* Parse a HTML table |
*/ |
parseTable: function() { |
var options = this.options, |
table = options.table, |
columns = this.columns, |
startRow = options.startRow || 0, |
endRow = options.endRow || Number.MAX_VALUE, |
startColumn = options.startColumn || 0, |
endColumn = options.endColumn || Number.MAX_VALUE; |
|
if (table) { |
|
if (typeof table === 'string') { |
table = doc.getElementById(table); |
} |
|
each(table.getElementsByTagName('tr'), function(tr, rowNo) { |
if (rowNo >= startRow && rowNo <= endRow) { |
each(tr.children, function(item, colNo) { |
if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) { |
if (!columns[colNo - startColumn]) { |
columns[colNo - startColumn] = []; |
} |
|
columns[colNo - startColumn][rowNo - startRow] = item.innerHTML; |
} |
}); |
} |
}); |
|
this.dataFound(); // continue |
} |
}, |
|
/** |
*/ |
parseGoogleSpreadsheet: function() { |
var self = this, |
options = this.options, |
googleSpreadsheetKey = options.googleSpreadsheetKey, |
columns = this.columns, |
startRow = options.startRow || 0, |
endRow = options.endRow || Number.MAX_VALUE, |
startColumn = options.startColumn || 0, |
endColumn = options.endColumn || Number.MAX_VALUE, |
gr, // google row |
gc; // google column |
|
if (googleSpreadsheetKey) { |
jQuery.ajax({ |
dataType: 'json', |
url: 'https://spreadsheets.google.com/feeds/cells/' + |
googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') + |
'/public/values?alt=json-in-script&callback=?', |
error: options.error, |
success: function(json) { |
// Prepare the data from the spreadsheat |
var cells = json.feed.entry, |
cell, |
cellCount = cells.length, |
colCount = 0, |
rowCount = 0, |
i; |
|
// First, find the total number of columns and rows that |
// are actually filled with data |
for (i = 0; i < cellCount; i++) { |
cell = cells[i]; |
colCount = Math.max(colCount, cell.gs$cell.col); |
rowCount = Math.max(rowCount, cell.gs$cell.row); |
} |
|
// Set up arrays containing the column data |
for (i = 0; i < colCount; i++) { |
if (i >= startColumn && i <= endColumn) { |
// Create new columns with the length of either end-start or rowCount |
columns[i - startColumn] = []; |
|
// Setting the length to avoid jslint warning |
columns[i - startColumn].length = Math.min(rowCount, endRow - startRow); |
} |
} |
|
// Loop over the cells and assign the value to the right |
// place in the column arrays |
for (i = 0; i < cellCount; i++) { |
cell = cells[i]; |
gr = cell.gs$cell.row - 1; // rows start at 1 |
gc = cell.gs$cell.col - 1; // columns start at 1 |
|
// If both row and col falls inside start and end |
// set the transposed cell value in the newly created columns |
if (gc >= startColumn && gc <= endColumn && |
gr >= startRow && gr <= endRow) { |
columns[gc - startColumn][gr - startRow] = cell.content.$t; |
} |
} |
|
// Insert null for empty spreadsheet cells (#5298) |
each(columns, function(column) { |
for (i = 0; i < column.length; i++) { |
if (column[i] === undefined) { |
column[i] = null; |
} |
} |
}); |
|
self.dataFound(); |
} |
}); |
} |
}, |
|
/** |
* Trim a string from whitespace |
*/ |
trim: function(str, inside) { |
if (typeof str === 'string') { |
str = str.replace(/^\s+|\s+$/g, ''); |
|
// Clear white space insdie the string, like thousands separators |
if (inside && /^[0-9\s]+$/.test(str)) { |
str = str.replace(/\s/g, ''); |
} |
|
if (this.decimalRegex) { |
str = str.replace(this.decimalRegex, '$1.$2'); |
} |
} |
return str; |
}, |
|
/** |
* Parse numeric cells in to number types and date types in to true dates. |
*/ |
parseTypes: function() { |
var columns = this.columns, |
col = columns.length; |
|
while (col--) { |
this.parseColumn(columns[col], col); |
} |
|
}, |
|
/** |
* Parse a single column. Set properties like .isDatetime and .isNumeric. |
*/ |
parseColumn: function(column, col) { |
var rawColumns = this.rawColumns, |
columns = this.columns, |
row = column.length, |
val, |
floatVal, |
trimVal, |
trimInsideVal, |
firstRowAsNames = this.firstRowAsNames, |
isXColumn = inArray(col, this.valueCount.xColumns) !== -1, |
dateVal, |
backup = [], |
diff, |
chartOptions = this.chartOptions, |
descending, |
columnTypes = this.options.columnTypes || [], |
columnType = columnTypes[col], |
forceCategory = isXColumn && ((chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category') || columnType === 'string'); |
|
if (!rawColumns[col]) { |
rawColumns[col] = []; |
} |
while (row--) { |
val = backup[row] || column[row]; |
|
trimVal = this.trim(val); |
trimInsideVal = this.trim(val, true); |
floatVal = parseFloat(trimInsideVal); |
|
// Set it the first time |
if (rawColumns[col][row] === undefined) { |
rawColumns[col][row] = trimVal; |
} |
|
// Disable number or date parsing by setting the X axis type to category |
if (forceCategory || (row === 0 && firstRowAsNames)) { |
column[row] = trimVal; |
|
} else if (+trimInsideVal === floatVal) { // is numeric |
|
column[row] = floatVal; |
|
// If the number is greater than milliseconds in a year, assume datetime |
if (floatVal > 365 * 24 * 3600 * 1000 && columnType !== 'float') { |
column.isDatetime = true; |
} else { |
column.isNumeric = true; |
} |
|
if (column[row + 1] !== undefined) { |
descending = floatVal > column[row + 1]; |
} |
|
// String, continue to determine if it is a date string or really a string |
} else { |
dateVal = this.parseDate(val); |
// Only allow parsing of dates if this column is an x-column |
if (isXColumn && isNumber(dateVal) && columnType !== 'float') { // is date |
backup[row] = val; |
column[row] = dateVal; |
column.isDatetime = true; |
|
// Check if the dates are uniformly descending or ascending. If they |
// are not, chances are that they are a different time format, so check |
// for alternative. |
if (column[row + 1] !== undefined) { |
diff = dateVal > column[row + 1]; |
if (diff !== descending && descending !== undefined) { |
if (this.alternativeFormat) { |
this.dateFormat = this.alternativeFormat; |
row = column.length; |
this.alternativeFormat = this.dateFormats[this.dateFormat].alternative; |
} else { |
column.unsorted = true; |
} |
} |
descending = diff; |
} |
|
} else { // string |
column[row] = trimVal === '' ? null : trimVal; |
if (row !== 0 && (column.isDatetime || column.isNumeric)) { |
column.mixed = true; |
} |
} |
} |
} |
|
// If strings are intermixed with numbers or dates in a parsed column, it is an indication |
// that parsing went wrong or the data was not intended to display as numbers or dates and |
// parsing is too aggressive. Fall back to categories. Demonstrated in the |
// highcharts/demo/column-drilldown sample. |
if (isXColumn && column.mixed) { |
columns[col] = rawColumns[col]; |
} |
|
// If the 0 column is date or number and descending, reverse all columns. |
if (isXColumn && descending && this.options.sort) { |
for (col = 0; col < columns.length; col++) { |
columns[col].reverse(); |
if (firstRowAsNames) { |
columns[col].unshift(columns[col].pop()); |
} |
} |
} |
}, |
|
/** |
* A collection of available date formats, extendable from the outside to support |
* custom date formats. |
*/ |
dateFormats: { |
'YYYY-mm-dd': { |
regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/, |
parser: function(match) { |
return Date.UTC(+match[1], match[2] - 1, +match[3]); |
} |
}, |
'dd/mm/YYYY': { |
regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/, |
parser: function(match) { |
return Date.UTC(+match[3], match[2] - 1, +match[1]); |
}, |
alternative: 'mm/dd/YYYY' // different format with the same regex |
}, |
'mm/dd/YYYY': { |
regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/, |
parser: function(match) { |
return Date.UTC(+match[3], match[1] - 1, +match[2]); |
} |
}, |
'dd/mm/YY': { |
regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/, |
parser: function(match) { |
return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]); |
}, |
alternative: 'mm/dd/YY' // different format with the same regex |
}, |
'mm/dd/YY': { |
regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/, |
parser: function(match) { |
return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]); |
} |
} |
}, |
|
/** |
* Parse a date and return it as a number. Overridable through options.parseDate. |
*/ |
parseDate: function(val) { |
var parseDate = this.options.parseDate, |
ret, |
key, |
format, |
dateFormat = this.options.dateFormat || this.dateFormat, |
match; |
|
if (parseDate) { |
ret = parseDate(val); |
|
} else if (typeof val === 'string') { |
// Auto-detect the date format the first time |
if (!dateFormat) { |
for (key in this.dateFormats) { |
format = this.dateFormats[key]; |
match = val.match(format.regex); |
if (match) { |
this.dateFormat = dateFormat = key; |
this.alternativeFormat = format.alternative; |
ret = format.parser(match); |
break; |
} |
} |
// Next time, use the one previously found |
} else { |
format = this.dateFormats[dateFormat]; |
match = val.match(format.regex); |
if (match) { |
ret = format.parser(match); |
} |
} |
// Fall back to Date.parse |
if (!match) { |
match = Date.parse(val); |
// External tools like Date.js and MooTools extend Date object and |
// returns a date. |
if (typeof match === 'object' && match !== null && match.getTime) { |
ret = match.getTime() - match.getTimezoneOffset() * 60000; |
|
// Timestamp |
} else if (isNumber(match)) { |
ret = match - (new Date(match)).getTimezoneOffset() * 60000; |
} |
} |
} |
return ret; |
}, |
|
/** |
* Reorganize rows into columns |
*/ |
rowsToColumns: function(rows) { |
var row, |
rowsLength, |
col, |
colsLength, |
columns; |
|
if (rows) { |
columns = []; |
rowsLength = rows.length; |
for (row = 0; row < rowsLength; row++) { |
colsLength = rows[row].length; |
for (col = 0; col < colsLength; col++) { |
if (!columns[col]) { |
columns[col] = []; |
} |
columns[col][row] = rows[row][col]; |
} |
} |
} |
return columns; |
}, |
|
/** |
* A hook for working directly on the parsed columns |
*/ |
parsed: function() { |
if (this.options.parsed) { |
return this.options.parsed.call(this, this.columns); |
} |
}, |
|
getFreeIndexes: function(numberOfColumns, seriesBuilders) { |
var s, |
i, |
freeIndexes = [], |
freeIndexValues = [], |
referencedIndexes; |
|
// Add all columns as free |
for (i = 0; i < numberOfColumns; i = i + 1) { |
freeIndexes.push(true); |
} |
|
// Loop all defined builders and remove their referenced columns |
for (s = 0; s < seriesBuilders.length; s = s + 1) { |
referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes(); |
|
for (i = 0; i < referencedIndexes.length; i = i + 1) { |
freeIndexes[referencedIndexes[i]] = false; |
} |
} |
|
// Collect the values for the free indexes |
for (i = 0; i < freeIndexes.length; i = i + 1) { |
if (freeIndexes[i]) { |
freeIndexValues.push(i); |
} |
} |
|
return freeIndexValues; |
}, |
|
/** |
* If a complete callback function is provided in the options, interpret the |
* columns into a Highcharts options object. |
*/ |
complete: 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++) { |
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 (type === 'category') { |
chartOptions.xAxis.uniqueNames = false; |
} |
} |
|
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); |
} |
} |
} |
}); |
|
// Register the Data prototype and data function on Highcharts |
Highcharts.Data = Data; |
Highcharts.data = function(options, chartOptions) { |
return new Data(options, chartOptions); |
}; |
|
// Extend Chart.init so that the Chart constructor accepts a new configuration |
// option group, data. |
Highcharts.wrap(Highcharts.Chart.prototype, 'init', function(proceed, userOptions, callback) { |
var chart = this; |
|
if (userOptions && userOptions.data) { |
Highcharts.data(Highcharts.extend(userOptions.data, { |
|
afterComplete: function(dataOptions) { |
var i, series; |
|
// Merge series configs |
if (userOptions.hasOwnProperty('series')) { |
if (typeof userOptions.series === 'object') { |
i = Math.max(userOptions.series.length, dataOptions.series.length); |
while (i--) { |
series = userOptions.series[i] || {}; |
userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]); |
} |
} else { // Allow merging in dataOptions.series (#2856) |
delete userOptions.series; |
} |
} |
|
// Do the merge |
userOptions = Highcharts.merge(dataOptions, userOptions); |
|
proceed.call(chart, userOptions, callback); |
} |
}), userOptions); |
} else { |
proceed.call(chart, userOptions, callback); |
} |
}); |
|
/** |
* Creates a new SeriesBuilder. A SeriesBuilder consists of a number |
* of ColumnReaders that reads columns and give them a name. |
* Ex: A series builder can be constructed to read column 3 as 'x' and |
* column 7 and 8 as 'y1' and 'y2'. |
* The output would then be points/rows of the form {x: 11, y1: 22, y2: 33} |
* |
* The name of the builder is taken from the second column. In the above |
* example it would be the column with index 7. |
* @constructor |
*/ |
SeriesBuilder = function() { |
this.readers = []; |
this.pointIsArray = true; |
}; |
|
/** |
* Populates readers with column indexes. A reader can be added without |
* a specific index and for those readers the index is taken sequentially |
* from the free columns (this is handled by the ColumnCursor instance). |
* @returns {boolean} |
*/ |
SeriesBuilder.prototype.populateColumns = function(freeIndexes) { |
var builder = this, |
enoughColumns = true; |
|
// Loop each reader and give it an index if its missing. |
// The freeIndexes.shift() will return undefined if there |
// are no more columns. |
each(builder.readers, function(reader) { |
if (reader.columnIndex === undefined) { |
reader.columnIndex = freeIndexes.shift(); |
} |
}); |
|
// Now, all readers should have columns mapped. If not |
// then return false to signal that this series should |
// not be added. |
each(builder.readers, function(reader) { |
if (reader.columnIndex === undefined) { |
enoughColumns = false; |
} |
}); |
|
return enoughColumns; |
}; |
|
/** |
* Reads a row from the dataset and returns a point or array depending |
* on the names of the readers. |
* @param columns |
* @param rowIndex |
* @returns {Array | Object} |
*/ |
SeriesBuilder.prototype.read = function(columns, rowIndex) { |
var builder = this, |
pointIsArray = builder.pointIsArray, |
point = pointIsArray ? [] : {}, |
columnIndexes; |
|
// Loop each reader and ask it to read its value. |
// Then, build an array or point based on the readers names. |
each(builder.readers, function(reader) { |
var value = columns[reader.columnIndex][rowIndex]; |
if (pointIsArray) { |
point.push(value); |
} else { |
point[reader.configName] = value; |
} |
}); |
|
// The name comes from the first column (excluding the x column) |
if (this.name === undefined && builder.readers.length >= 2) { |
columnIndexes = builder.getReferencedColumnIndexes(); |
if (columnIndexes.length >= 2) { |
// remove the first one (x col) |
columnIndexes.shift(); |
|
// Sort the remaining |
columnIndexes.sort(); |
|
// Now use the lowest index as name column |
this.name = columns[columnIndexes.shift()].name; |
} |
} |
|
return point; |
}; |
|
/** |
* Creates and adds ColumnReader from the given columnIndex and configName. |
* ColumnIndex can be undefined and in that case the reader will be given |
* an index when columns are populated. |
* @param columnIndex {Number | undefined} |
* @param configName |
*/ |
SeriesBuilder.prototype.addColumnReader = function(columnIndex, configName) { |
this.readers.push({ |
columnIndex: columnIndex, |
configName: configName |
}); |
|
if (!(configName === 'x' || configName === 'y' || configName === undefined)) { |
this.pointIsArray = false; |
} |
}; |
|
/** |
* Returns an array of column indexes that the builder will use when |
* reading data. |
* @returns {Array} |
*/ |
SeriesBuilder.prototype.getReferencedColumnIndexes = function() { |
var i, |
referencedColumnIndexes = [], |
columnReader; |
|
for (i = 0; i < this.readers.length; i = i + 1) { |
columnReader = this.readers[i]; |
if (columnReader.columnIndex !== undefined) { |
referencedColumnIndexes.push(columnReader.columnIndex); |
} |
} |
|
return referencedColumnIndexes; |
}; |
|
/** |
* Returns true if the builder has a reader for the given configName. |
* @param configName |
* @returns {boolean} |
*/ |
SeriesBuilder.prototype.hasReader = function(configName) { |
var i, columnReader; |
for (i = 0; i < this.readers.length; i = i + 1) { |
columnReader = this.readers[i]; |
if (columnReader.configName === configName) { |
return true; |
} |
} |
// Else return undefined |
}; |
|
}(Highcharts)); |
})); |