data.src.js 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Data module
  4. *
  5. * (c) 2012-2017 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. (function (H) {
  18. /**
  19. * (c) 2010-2017 Christer Vasseng, Torstein Honsi
  20. *
  21. * License: www.highcharts.com/license
  22. */
  23. /**
  24. * @typedef {Object} AjaxSettings
  25. * @property {String} url - The URL to call
  26. * @property {('get'|'post'|'update'|'delete')} type - The verb to use
  27. * @property {('json'|'xml'|'text'|'octet')} dataType - The data type expected
  28. * @property {Function} success - Function to call on success
  29. * @property {Function} error - Function to call on error
  30. * @property {Object} data - The payload to send
  31. * @property {Object} headers - The headers; keyed on header name
  32. */
  33. /**
  34. * Perform an Ajax call.
  35. *
  36. * @memberof Highcharts
  37. * @param {AjaxSettings} - The Ajax settings to use
  38. *
  39. */
  40. H.ajax = function (attr) {
  41. var options = H.merge(true, {
  42. url: false,
  43. type: 'GET',
  44. dataType: 'json',
  45. success: false,
  46. error: false,
  47. data: false,
  48. headers: {}
  49. }, attr),
  50. headers = {
  51. json: 'application/json',
  52. xml: 'application/xml',
  53. text: 'text/plain',
  54. octet: 'application/octet-stream'
  55. },
  56. r = new XMLHttpRequest();
  57. function handleError(xhr, err) {
  58. if (options.error) {
  59. options.error(xhr, err);
  60. } else {
  61. // Maybe emit a highcharts error event here
  62. }
  63. }
  64. if (!options.url) {
  65. return false;
  66. }
  67. r.open(options.type.toUpperCase(), options.url, true);
  68. r.setRequestHeader(
  69. 'Content-Type',
  70. headers[options.dataType] || headers.text
  71. );
  72. H.objectEach(options.headers, function (val, key) {
  73. r.setRequestHeader(key, val);
  74. });
  75. r.onreadystatechange = function () {
  76. var res;
  77. if (r.readyState === 4) {
  78. if (r.status === 200) {
  79. res = r.responseText;
  80. if (options.dataType === 'json') {
  81. try {
  82. res = JSON.parse(res);
  83. } catch (e) {
  84. return handleError(r, e);
  85. }
  86. }
  87. return options.success && options.success(res);
  88. }
  89. handleError(r, r.responseText);
  90. }
  91. };
  92. try {
  93. options.data = JSON.stringify(options.data);
  94. } catch (e) {}
  95. r.send(options.data || true);
  96. };
  97. }(Highcharts));
  98. (function (Highcharts) {
  99. /**
  100. * Data module
  101. *
  102. * (c) 2012-2017 Torstein Honsi
  103. *
  104. * License: www.highcharts.com/license
  105. */
  106. // Utilities
  107. var addEvent = Highcharts.addEvent,
  108. Chart = Highcharts.Chart,
  109. win = Highcharts.win,
  110. doc = win.document,
  111. each = Highcharts.each,
  112. objectEach = Highcharts.objectEach,
  113. pick = Highcharts.pick,
  114. inArray = Highcharts.inArray,
  115. isNumber = Highcharts.isNumber,
  116. merge = Highcharts.merge,
  117. splat = Highcharts.splat,
  118. fireEvent = Highcharts.fireEvent,
  119. some = Highcharts.some,
  120. SeriesBuilder;
  121. /**
  122. * The Data module provides a simplified interface for adding data to
  123. * a chart from sources like CVS, HTML tables or grid views. See also
  124. * the [tutorial article on the Data module](http://www.highcharts.com/docs/working-
  125. * with-data/data-module).
  126. *
  127. * It requires the `modules/data.js` file to be loaded.
  128. *
  129. * Please note that the default way of adding data in Highcharts, without
  130. * the need of a module, is through the [series.data](#series.data)
  131. * option.
  132. *
  133. * @sample {highcharts} highcharts/demo/column-parsed/ HTML table
  134. * @sample {highcharts} highcharts/data/csv/ CSV
  135. * @since 4.0
  136. * @apioption data
  137. */
  138. /**
  139. * A callback function to modify the CSV before parsing it. Return the modified
  140. * string.
  141. *
  142. * @type {Function}
  143. * @sample {highcharts} highcharts/demo/line-ajax/ Modify CSV before parse
  144. * @since 6.1
  145. * @apioption data.beforeParse
  146. */
  147. /**
  148. * A two-dimensional array representing the input data on tabular form.
  149. * This input can be used when the data is already parsed, for example
  150. * from a grid view component. Each cell can be a string or number.
  151. * If not switchRowsAndColumns is set, the columns are interpreted as
  152. * series.
  153. *
  154. * @type {Array<Array<Mixed>>}
  155. * @see [data.rows](#data.rows)
  156. * @sample {highcharts} highcharts/data/columns/ Columns
  157. * @since 4.0
  158. * @apioption data.columns
  159. */
  160. /**
  161. * The callback that is evaluated when the data is finished loading,
  162. * optionally from an external source, and parsed. The first argument
  163. * passed is a finished chart options object, containing the series.
  164. * These options can be extended with additional options and passed
  165. * directly to the chart constructor.
  166. *
  167. * @type {Function}
  168. * @see [data.parsed](#data.parsed)
  169. * @sample {highcharts} highcharts/data/complete/ Modify data on complete
  170. * @since 4.0
  171. * @apioption data.complete
  172. */
  173. /**
  174. * A comma delimited string to be parsed. Related options are [startRow](
  175. * #data.startRow), [endRow](#data.endRow), [startColumn](#data.startColumn)
  176. * and [endColumn](#data.endColumn) to delimit what part of the table
  177. * is used. The [lineDelimiter](#data.lineDelimiter) and [itemDelimiter](
  178. * #data.itemDelimiter) options define the CSV delimiter formats.
  179. *
  180. * The built-in CSV parser doesn't support all flavours of CSV, so in
  181. * some cases it may be necessary to use an external CSV parser. See
  182. * [this example](http://jsfiddle.net/highcharts/u59176h4/) of parsing
  183. * CSV through the MIT licensed [Papa Parse](http://papaparse.com/)
  184. * library.
  185. *
  186. * @type {String}
  187. * @sample {highcharts} highcharts/data/csv/ Data from CSV
  188. * @since 4.0
  189. * @apioption data.csv
  190. */
  191. /**
  192. * Which of the predefined date formats in Date.prototype.dateFormats
  193. * to use to parse date values. Defaults to a best guess based on what
  194. * format gives valid and ordered dates.
  195. *
  196. * Valid options include:
  197. *
  198. * * `YYYY/mm/dd`
  199. * * `dd/mm/YYYY`
  200. * * `mm/dd/YYYY`
  201. * * `dd/mm/YY`
  202. * * `mm/dd/YY`
  203. *
  204. * @validvalue [undefined, "YYYY/mm/dd", "dd/mm/YYYY", "mm/dd/YYYY",
  205. * "dd/mm/YYYY", "dd/mm/YY", "mm/dd/YY"]
  206. * @type {String}
  207. * @see [data.parseDate](#data.parseDate)
  208. * @sample {highcharts} highcharts/data/dateformat-auto/ Best guess date format
  209. * @since 4.0
  210. * @apioption data.dateFormat
  211. */
  212. /**
  213. * The decimal point used for parsing numbers in the CSV.
  214. *
  215. * If both this and data.delimiter is set to false, the parser will
  216. * attempt to deduce the decimal point automatically.
  217. *
  218. * @type {String}
  219. * @sample {highcharts} highcharts/data/delimiters/ Comma as decimal point
  220. * @default .
  221. * @since 4.1.0
  222. * @apioption data.decimalPoint
  223. */
  224. /**
  225. * In tabular input data, the last column (indexed by 0) to use. Defaults
  226. * to the last column containing data.
  227. *
  228. * @type {Number}
  229. * @sample {highcharts} highcharts/data/start-end/ Limited data
  230. * @since 4.0
  231. * @apioption data.endColumn
  232. */
  233. /**
  234. * In tabular input data, the last row (indexed by 0) to use. Defaults
  235. * to the last row containing data.
  236. *
  237. * @type {Number}
  238. * @sample {highcharts} highcharts/data/start-end/ Limited data
  239. * @since 4.0.4
  240. * @apioption data.endRow
  241. */
  242. /**
  243. * Whether to use the first row in the data set as series names.
  244. *
  245. * @type {Boolean}
  246. * @sample {highcharts} highcharts/data/start-end/ Don't get series names from the CSV
  247. * @sample {highstock} highcharts/data/start-end/ Don't get series names from the CSV
  248. * @default true
  249. * @since 4.1.0
  250. * @product highcharts highstock
  251. * @apioption data.firstRowAsNames
  252. */
  253. /**
  254. * The key for a Google Spreadsheet to load. See [general information
  255. * on GS](https://developers.google.com/gdata/samples/spreadsheet_sample).
  256. *
  257. * @type {String}
  258. * @sample {highcharts} highcharts/data/google-spreadsheet/
  259. * Load a Google Spreadsheet
  260. * @since 4.0
  261. * @apioption data.googleSpreadsheetKey
  262. */
  263. /**
  264. * The Google Spreadsheet worksheet to use in combination with
  265. * [googleSpreadsheetKey](#data.googleSpreadsheetKey). The available id's from
  266. * your sheet can be read from `https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic`.
  267. *
  268. * @type {String}
  269. * @sample {highcharts} highcharts/data/google-spreadsheet/ Load a Google Spreadsheet
  270. * @since 4.0
  271. * @apioption data.googleSpreadsheetWorksheet
  272. */
  273. /**
  274. * Item or cell delimiter for parsing CSV. Defaults to the tab character
  275. * `\t` if a tab character is found in the CSV string, if not it defaults
  276. * to `,`.
  277. *
  278. * If this is set to false or undefined, the parser will attempt to deduce
  279. * the delimiter automatically.
  280. *
  281. * @type {String}
  282. * @sample {highcharts} highcharts/data/delimiters/ Delimiters
  283. * @since 4.0
  284. * @apioption data.itemDelimiter
  285. */
  286. /**
  287. * Line delimiter for parsing CSV.
  288. *
  289. * @type {String}
  290. * @sample {highcharts} highcharts/data/delimiters/ Delimiters
  291. * @default \n
  292. * @since 4.0
  293. * @apioption data.lineDelimiter
  294. */
  295. /**
  296. * A callback function to parse string representations of dates into
  297. * JavaScript timestamps. Should return an integer timestamp on success.
  298. *
  299. * @type {Function}
  300. * @see [dateFormat](#data.dateFormat)
  301. * @since 4.0
  302. * @apioption data.parseDate
  303. */
  304. /**
  305. * A callback function to access the parsed columns, the two-dimentional
  306. * input data array directly, before they are interpreted into series
  307. * data and categories. Return `false` to stop completion, or call
  308. * `this.complete()` to continue async.
  309. *
  310. * @type {Function}
  311. * @see [data.complete](#data.complete)
  312. * @sample {highcharts} highcharts/data/parsed/ Modify data after parse
  313. * @since 4.0
  314. * @apioption data.parsed
  315. */
  316. /**
  317. * The same as the columns input option, but defining rows intead of
  318. * columns.
  319. *
  320. * @type {Array<Array<Mixed>>}
  321. * @see [data.columns](#data.columns)
  322. * @sample {highcharts} highcharts/data/rows/ Data in rows
  323. * @since 4.0
  324. * @apioption data.rows
  325. */
  326. /**
  327. * An array containing object with Point property names along with what
  328. * column id the property should be taken from.
  329. *
  330. * @type {Array<Object>}
  331. * @sample {highcharts} highcharts/data/seriesmapping-label/ Label from data set
  332. * @since 4.0.4
  333. * @apioption data.seriesMapping
  334. */
  335. /**
  336. * In tabular input data, the first column (indexed by 0) to use.
  337. *
  338. * @type {Number}
  339. * @sample {highcharts} highcharts/data/start-end/ Limited data
  340. * @default 0
  341. * @since 4.0
  342. * @apioption data.startColumn
  343. */
  344. /**
  345. * In tabular input data, the first row (indexed by 0) to use.
  346. *
  347. * @type {Number}
  348. * @sample {highcharts} highcharts/data/start-end/ Limited data
  349. * @default 0
  350. * @since 4.0
  351. * @apioption data.startRow
  352. */
  353. /**
  354. * Switch rows and columns of the input data, so that `this.columns`
  355. * effectively becomes the rows of the data set, and the rows are interpreted
  356. * as series.
  357. *
  358. * @type {Boolean}
  359. * @sample {highcharts} highcharts/data/switchrowsandcolumns/ Switch rows and columns
  360. * @default false
  361. * @since 4.0
  362. * @apioption data.switchRowsAndColumns
  363. */
  364. /**
  365. * A HTML table or the id of such to be parsed as input data. Related
  366. * options are `startRow`, `endRow`, `startColumn` and `endColumn` to
  367. * delimit what part of the table is used.
  368. *
  369. * @type {String|HTMLElement}
  370. * @sample {highcharts} highcharts/demo/column-parsed/ Parsed table
  371. * @since 4.0
  372. * @apioption data.table
  373. */
  374. /**
  375. * A URL to a remote CSV dataset.
  376. * Will be fetched when the chart is created using Ajax.
  377. *
  378. * @type {String}
  379. * @sample highcharts/data/livedata-columns
  380. * Categorized bar chart with CSV and live polling
  381. * @sample highcharts/data/livedata-csv
  382. * Time based line chart with CSV and live polling
  383. * @apioption data.csvURL
  384. */
  385. /**
  386. * A URL to a remote JSON dataset, structured as a row array.
  387. * Will be fetched when the chart is created using Ajax.
  388. *
  389. * @type {String}
  390. * @sample highcharts/data/livedata-rows
  391. * Rows with live polling
  392. * @apioption data.rowsURL
  393. */
  394. /**
  395. * A URL to a remote JSON dataset, structured as a column array.
  396. * Will be fetched when the chart is created using Ajax.
  397. *
  398. * @type {String}
  399. * @sample highcharts/data/livedata-columns
  400. * Columns with live polling
  401. * @apioption data.columnsURL
  402. */
  403. /**
  404. * Sets the refresh rate for data polling when importing remote dataset by
  405. * setting [data.csvURL](data.csvURL), [data.rowsURL](data.rowsURL),
  406. * [data.columnsURL](data.columnsURL), or
  407. * [data.googleSpreadsheetKey](data.googleSpreadsheetKey).
  408. *
  409. * Note that polling must be enabled by setting
  410. * [data.enablePolling](data.enablePolling) to true.
  411. *
  412. * The value is the number of seconds between pollings.
  413. * It cannot be set to less than 1 second.
  414. *
  415. * @default 1
  416. * @type {Number}
  417. * @sample highcharts/demo/live-data
  418. * Live data with user set refresh rate
  419. * @apioption data.dataRefreshRate
  420. */
  421. /**
  422. * Enables automatic refetching of remote datasets every _n_ seconds (defined by
  423. * setting [data.dataRefreshRate](data.dataRefreshRate)).
  424. *
  425. * Only works when either [data.csvURL](data.csvURL),
  426. * [data.rowsURL](data.rowsURL), [data.columnsURL](data.columnsURL), or
  427. * [data.googleSpreadsheetKey](data.googleSpreadsheetKey).
  428. *
  429. * @sample highcharts/demo/live-data
  430. * Live data
  431. * @sample highcharts/data/livedata-columns
  432. * Categorized bar chart with CSV and live polling
  433. *
  434. * @type {Bool}
  435. * @default false
  436. * @apioption data.enablePolling
  437. */
  438. // The Data constructor
  439. var Data = function (dataOptions, chartOptions, chart) {
  440. this.init(dataOptions, chartOptions, chart);
  441. };
  442. // Set the prototype properties
  443. Highcharts.extend(Data.prototype, {
  444. /**
  445. * Initialize the Data object with the given options
  446. */
  447. init: function (options, chartOptions, chart) {
  448. var decimalPoint = options.decimalPoint,
  449. hasData;
  450. if (chartOptions) {
  451. this.chartOptions = chartOptions;
  452. }
  453. if (chart) {
  454. this.chart = chart;
  455. }
  456. if (decimalPoint !== '.' && decimalPoint !== ',') {
  457. decimalPoint = undefined;
  458. }
  459. this.options = options;
  460. this.columns = (
  461. options.columns ||
  462. this.rowsToColumns(options.rows) ||
  463. []
  464. );
  465. this.firstRowAsNames = pick(
  466. options.firstRowAsNames,
  467. this.firstRowAsNames,
  468. true
  469. );
  470. this.decimalRegex = (
  471. decimalPoint &&
  472. new RegExp('^(-?[0-9]+)' + decimalPoint + '([0-9]+)$') // eslint-disable-line security/detect-non-literal-regexp
  473. );
  474. // This is a two-dimensional array holding the raw, trimmed string
  475. // values with the same organisation as the columns array. It makes it
  476. // possible for example to revert from interpreted timestamps to
  477. // string-based categories.
  478. this.rawColumns = [];
  479. // No need to parse or interpret anything
  480. if (this.columns.length) {
  481. this.dataFound();
  482. hasData = true;
  483. }
  484. if (!hasData) {
  485. // Fetch live data
  486. hasData = this.fetchLiveData();
  487. }
  488. if (!hasData) {
  489. // Parse a CSV string if options.csv is given. The parseCSV function
  490. // returns a columns array, if it has no length, we have no data
  491. hasData = Boolean(this.parseCSV().length);
  492. }
  493. if (!hasData) {
  494. // Parse a HTML table if options.table is given
  495. hasData = Boolean(this.parseTable().length);
  496. }
  497. if (!hasData) {
  498. // Parse a Google Spreadsheet
  499. hasData = this.parseGoogleSpreadsheet();
  500. }
  501. if (!hasData && options.afterComplete) {
  502. options.afterComplete();
  503. }
  504. },
  505. /**
  506. * Get the column distribution. For example, a line series takes a single
  507. * column for Y values. A range series takes two columns for low and high
  508. * values respectively, and an OHLC series takes four columns.
  509. */
  510. getColumnDistribution: function () {
  511. var chartOptions = this.chartOptions,
  512. options = this.options,
  513. xColumns = [],
  514. getValueCount = function (type) {
  515. return (
  516. Highcharts.seriesTypes[type || 'line'].prototype
  517. .pointArrayMap ||
  518. [0]
  519. ).length;
  520. },
  521. getPointArrayMap = function (type) {
  522. return Highcharts.seriesTypes[type || 'line']
  523. .prototype.pointArrayMap;
  524. },
  525. globalType = (
  526. chartOptions &&
  527. chartOptions.chart &&
  528. chartOptions.chart.type
  529. ),
  530. individualCounts = [],
  531. seriesBuilders = [],
  532. seriesIndex = 0,
  533. i;
  534. each((chartOptions && chartOptions.series) || [], function (series) {
  535. individualCounts.push(getValueCount(series.type || globalType));
  536. });
  537. // Collect the x-column indexes from seriesMapping
  538. each((options && options.seriesMapping) || [], function (mapping) {
  539. xColumns.push(mapping.x || 0);
  540. });
  541. // If there are no defined series with x-columns, use the first column
  542. // as x column
  543. if (xColumns.length === 0) {
  544. xColumns.push(0);
  545. }
  546. // Loop all seriesMappings and constructs SeriesBuilders from
  547. // the mapping options.
  548. each((options && options.seriesMapping) || [], function (mapping) {
  549. var builder = new SeriesBuilder(),
  550. numberOfValueColumnsNeeded = individualCounts[seriesIndex] ||
  551. getValueCount(globalType),
  552. seriesArr = (chartOptions && chartOptions.series) || [],
  553. series = seriesArr[seriesIndex] || {},
  554. pointArrayMap = getPointArrayMap(series.type || globalType) ||
  555. ['y'];
  556. // Add an x reader from the x property or from an undefined column
  557. // if the property is not set. It will then be auto populated later.
  558. builder.addColumnReader(mapping.x, 'x');
  559. // Add all column mappings
  560. objectEach(mapping, function (val, name) {
  561. if (name !== 'x') {
  562. builder.addColumnReader(val, name);
  563. }
  564. });
  565. // Add missing columns
  566. for (i = 0; i < numberOfValueColumnsNeeded; i++) {
  567. if (!builder.hasReader(pointArrayMap[i])) {
  568. // Create and add a column reader for the next free column
  569. // index
  570. builder.addColumnReader(undefined, pointArrayMap[i]);
  571. }
  572. }
  573. seriesBuilders.push(builder);
  574. seriesIndex++;
  575. });
  576. var globalPointArrayMap = getPointArrayMap(globalType);
  577. if (globalPointArrayMap === undefined) {
  578. globalPointArrayMap = ['y'];
  579. }
  580. this.valueCount = {
  581. global: getValueCount(globalType),
  582. xColumns: xColumns,
  583. individual: individualCounts,
  584. seriesBuilders: seriesBuilders,
  585. globalPointArrayMap: globalPointArrayMap
  586. };
  587. },
  588. /**
  589. * When the data is parsed into columns, either by CSV, table, GS or direct
  590. * input, continue with other operations.
  591. */
  592. dataFound: function () {
  593. if (this.options.switchRowsAndColumns) {
  594. this.columns = this.rowsToColumns(this.columns);
  595. }
  596. // Interpret the info about series and columns
  597. this.getColumnDistribution();
  598. // Interpret the values into right types
  599. this.parseTypes();
  600. // Handle columns if a handleColumns callback is given
  601. if (this.parsed() !== false) {
  602. // Complete if a complete callback is given
  603. this.complete();
  604. }
  605. },
  606. /**
  607. * Parse a CSV input string
  608. */
  609. parseCSV: function (inOptions) {
  610. var self = this,
  611. options = inOptions || this.options,
  612. csv = options.csv,
  613. columns,
  614. startRow = (
  615. typeof options.startRow !== 'undefined' && options.startRow ?
  616. options.startRow :
  617. 0
  618. ),
  619. endRow = options.endRow || Number.MAX_VALUE,
  620. startColumn = (
  621. typeof options.startColumn !== 'undefined' &&
  622. options.startColumn
  623. ) ? options.startColumn : 0,
  624. endColumn = options.endColumn || Number.MAX_VALUE,
  625. itemDelimiter,
  626. lines,
  627. rowIt = 0,
  628. // activeRowNo = 0,
  629. dataTypes = [],
  630. // We count potential delimiters in the prepass, and use the
  631. // result as the basis of half-intelligent guesses.
  632. potDelimiters = {
  633. ',': 0,
  634. ';': 0,
  635. '\t': 0
  636. };
  637. columns = this.columns = [];
  638. /*
  639. This implementation is quite verbose. It will be shortened once
  640. it's stable and passes all the test.
  641. It's also not written with speed in mind, instead everything is
  642. very seggregated, and there a several redundant loops.
  643. This is to make it easier to stabilize the code initially.
  644. We do a pre-pass on the first 4 rows to make some intelligent
  645. guesses on the set. Guessed delimiters are in this pass counted.
  646. Auto detecting delimiters
  647. - If we meet a quoted string, the next symbol afterwards
  648. (that's not \s, \t) is the delimiter
  649. - If we meet a date, the next symbol afterwards is the delimiter
  650. Date formats
  651. - If we meet a column with date formats, check all of them to
  652. see if one of the potential months crossing 12. If it does,
  653. we now know the format
  654. It would make things easier to guess the delimiter before
  655. doing the actual parsing.
  656. General rules:
  657. - Quoting is allowed, e.g: "Col 1",123,321
  658. - Quoting is optional, e.g.: Col1,123,321
  659. - Doubble quoting is escaping, e.g. "Col ""Hello world""",123
  660. - Spaces are considered part of the data: Col1 ,123
  661. - New line is always the row delimiter
  662. - Potential column delimiters are , ; \t
  663. - First row may optionally contain headers
  664. - The last row may or may not have a row delimiter
  665. - Comments are optionally supported, in which case the comment
  666. must start at the first column, and the rest of the line will
  667. be ignored
  668. */
  669. // Parse a single row
  670. function parseRow(columnStr, rowNumber, noAdd, callbacks) {
  671. var i = 0,
  672. c = '',
  673. cl = '',
  674. cn = '',
  675. token = '',
  676. actualColumn = 0,
  677. column = 0;
  678. function read(j) {
  679. c = columnStr[j];
  680. cl = columnStr[j - 1];
  681. cn = columnStr[j + 1];
  682. }
  683. function pushType(type) {
  684. if (dataTypes.length < column + 1) {
  685. dataTypes.push([type]);
  686. }
  687. if (dataTypes[column][dataTypes[column].length - 1] !== type) {
  688. dataTypes[column].push(type);
  689. }
  690. }
  691. function push() {
  692. if (startColumn > actualColumn || actualColumn > endColumn) {
  693. // Skip this column, but increment the column count (#7272)
  694. ++actualColumn;
  695. token = '';
  696. return;
  697. }
  698. if (!isNaN(parseFloat(token)) && isFinite(token)) {
  699. token = parseFloat(token);
  700. pushType('number');
  701. } else if (!isNaN(Date.parse(token))) {
  702. token = token.replace(/\//g, '-');
  703. pushType('date');
  704. } else {
  705. pushType('string');
  706. }
  707. if (columns.length < column + 1) {
  708. columns.push([]);
  709. }
  710. if (!noAdd) {
  711. // Don't push - if there's a varrying amount of columns
  712. // for each row, pushing will skew everything down n slots
  713. columns[column][rowNumber] = token;
  714. }
  715. token = '';
  716. ++column;
  717. ++actualColumn;
  718. }
  719. if (!columnStr.trim().length) {
  720. return;
  721. }
  722. if (columnStr.trim()[0] === '#') {
  723. return;
  724. }
  725. for (; i < columnStr.length; i++) {
  726. read(i);
  727. // Quoted string
  728. if (c === '#') {
  729. // The rest of the row is a comment
  730. push();
  731. return;
  732. } else if (c === '"') {
  733. read(++i);
  734. while (i < columnStr.length) {
  735. if (c === '"' && cl !== '"' && cn !== '"') {
  736. break;
  737. }
  738. if (c !== '"' || (c === '"' && cl !== '"')) {
  739. token += c;
  740. }
  741. read(++i);
  742. }
  743. // Perform "plugin" handling
  744. } else if (callbacks && callbacks[c]) {
  745. if (callbacks[c](c, token)) {
  746. push();
  747. }
  748. // Delimiter - push current token
  749. } else if (c === itemDelimiter) {
  750. push();
  751. // Actual column data
  752. } else {
  753. token += c;
  754. }
  755. }
  756. push();
  757. }
  758. // Attempt to guess the delimiter
  759. // We do a separate parse pass here because we need
  760. // to count potential delimiters softly without making any assumptions.
  761. function guessDelimiter(lines) {
  762. var points = 0,
  763. commas = 0,
  764. guessed = false;
  765. some(lines, function (columnStr, i) {
  766. var inStr = false,
  767. c,
  768. cn,
  769. cl,
  770. token = ''
  771. ;
  772. // We should be able to detect dateformats within 13 rows
  773. if (i > 13) {
  774. return true;
  775. }
  776. for (var j = 0; j < columnStr.length; j++) {
  777. c = columnStr[j];
  778. cn = columnStr[j + 1];
  779. cl = columnStr[j - 1];
  780. if (c === '#') {
  781. // Skip the rest of the line - it's a comment
  782. return;
  783. } else if (c === '"') {
  784. if (inStr) {
  785. if (cl !== '"' && cn !== '"') {
  786. while (cn === ' ' && j < columnStr.length) {
  787. cn = columnStr[++j];
  788. }
  789. // After parsing a string, the next non-blank
  790. // should be a delimiter if the CSV is properly
  791. // formed.
  792. if (typeof potDelimiters[cn] !== 'undefined') {
  793. potDelimiters[cn]++;
  794. }
  795. inStr = false;
  796. }
  797. } else {
  798. inStr = true;
  799. }
  800. } else if (typeof potDelimiters[c] !== 'undefined') {
  801. token = token.trim();
  802. if (!isNaN(Date.parse(token))) {
  803. potDelimiters[c]++;
  804. } else if (isNaN(token) || !isFinite(token)) {
  805. potDelimiters[c]++;
  806. }
  807. token = '';
  808. } else {
  809. token += c;
  810. }
  811. if (c === ',') {
  812. commas++;
  813. }
  814. if (c === '.') {
  815. points++;
  816. }
  817. }
  818. });
  819. // Count the potential delimiters.
  820. // This could be improved by checking if the number of delimiters
  821. // equals the number of columns - 1
  822. if (potDelimiters[';'] > potDelimiters[',']) {
  823. guessed = ';';
  824. } else if (potDelimiters[','] > potDelimiters[';']) {
  825. guessed = ',';
  826. } else {
  827. // No good guess could be made..
  828. guessed = ',';
  829. }
  830. // Try to deduce the decimal point if it's not explicitly set.
  831. // If both commas or points is > 0 there is likely an issue
  832. if (!options.decimalPoint) {
  833. if (points > commas) {
  834. options.decimalPoint = '.';
  835. } else {
  836. options.decimalPoint = ',';
  837. }
  838. // Apply a new decimal regex based on the presumed decimal sep.
  839. self.decimalRegex = new RegExp( // eslint-disable-line security/detect-non-literal-regexp
  840. '^(-?[0-9]+)' +
  841. options.decimalPoint +
  842. '([0-9]+)$'
  843. );
  844. }
  845. return guessed;
  846. }
  847. /* Tries to guess the date format
  848. * - Check if either month candidate exceeds 12
  849. * - Check if year is missing (use current year)
  850. * - Check if a shortened year format is used (e.g. 1/1/99)
  851. * - If no guess can be made, the user must be prompted
  852. * data is the data to deduce a format based on
  853. */
  854. function deduceDateFormat(data, limit) {
  855. var format = 'YYYY/mm/dd',
  856. thing,
  857. guessedFormat,
  858. calculatedFormat,
  859. i = 0,
  860. madeDeduction = false,
  861. // candidates = {},
  862. stable = [],
  863. max = [],
  864. j;
  865. if (!limit || limit > data.length) {
  866. limit = data.length;
  867. }
  868. for (; i < limit; i++) {
  869. if (
  870. typeof data[i] !== 'undefined' &&
  871. data[i] && data[i].length
  872. ) {
  873. thing = data[i]
  874. .trim()
  875. .replace(/\//g, ' ')
  876. .replace(/\-/g, ' ')
  877. .split(' ');
  878. guessedFormat = [
  879. '',
  880. '',
  881. ''
  882. ];
  883. for (j = 0; j < thing.length; j++) {
  884. if (j < guessedFormat.length) {
  885. thing[j] = parseInt(thing[j], 10);
  886. if (thing[j]) {
  887. max[j] = (!max[j] || max[j] < thing[j]) ?
  888. thing[j] :
  889. max[j];
  890. if (typeof stable[j] !== 'undefined') {
  891. if (stable[j] !== thing[j]) {
  892. stable[j] = false;
  893. }
  894. } else {
  895. stable[j] = thing[j];
  896. }
  897. if (thing[j] > 31) {
  898. if (thing[j] < 100) {
  899. guessedFormat[j] = 'YY';
  900. } else {
  901. guessedFormat[j] = 'YYYY';
  902. }
  903. // madeDeduction = true;
  904. } else if (thing[j] > 12 && thing[j] <= 31) {
  905. guessedFormat[j] = 'dd';
  906. madeDeduction = true;
  907. } else if (!guessedFormat[j].length) {
  908. guessedFormat[j] = 'mm';
  909. }
  910. }
  911. }
  912. }
  913. }
  914. }
  915. if (madeDeduction) {
  916. // This handles a few edge cases with hard to guess dates
  917. for (j = 0; j < stable.length; j++) {
  918. if (stable[j] !== false) {
  919. if (
  920. max[j] > 12 &&
  921. guessedFormat[j] !== 'YY' &&
  922. guessedFormat[j] !== 'YYYY'
  923. ) {
  924. guessedFormat[j] = 'YY';
  925. }
  926. } else if (max[j] > 12 && guessedFormat[j] === 'mm') {
  927. guessedFormat[j] = 'dd';
  928. }
  929. }
  930. // If the middle one is dd, and the last one is dd,
  931. // the last should likely be year.
  932. if (guessedFormat.length === 3 &&
  933. guessedFormat[1] === 'dd' &&
  934. guessedFormat[2] === 'dd') {
  935. guessedFormat[2] = 'YY';
  936. }
  937. calculatedFormat = guessedFormat.join('/');
  938. // If the caculated format is not valid, we need to present an
  939. // error.
  940. if (
  941. !(options.dateFormats || self.dateFormats)[calculatedFormat]
  942. ) {
  943. // This should emit an event instead
  944. fireEvent('deduceDateFailed');
  945. return format;
  946. }
  947. return calculatedFormat;
  948. }
  949. return format;
  950. }
  951. /* Figure out the best axis types for the data
  952. * - If the first column is a number, we're good
  953. * - If the first column is a date, set to date/time
  954. * - If the first column is a string, set to categories
  955. */
  956. function deduceAxisTypes() {
  957. }
  958. if (csv && options.beforeParse) {
  959. csv = options.beforeParse.call(this, csv);
  960. }
  961. if (csv) {
  962. lines = csv
  963. .replace(/\r\n/g, '\n') // Unix
  964. .replace(/\r/g, '\n') // Mac
  965. .split(options.lineDelimiter || '\n');
  966. if (!startRow || startRow < 0) {
  967. startRow = 0;
  968. }
  969. if (!endRow || endRow >= lines.length) {
  970. endRow = lines.length - 1;
  971. }
  972. if (options.itemDelimiter) {
  973. itemDelimiter = options.itemDelimiter;
  974. } else {
  975. itemDelimiter = null;
  976. itemDelimiter = guessDelimiter(lines);
  977. }
  978. var offset = 0;
  979. for (rowIt = startRow; rowIt <= endRow; rowIt++) {
  980. if (lines[rowIt][0] === '#') {
  981. offset++;
  982. } else {
  983. parseRow(lines[rowIt], rowIt - startRow - offset);
  984. }
  985. }
  986. // //Make sure that there's header columns for everything
  987. // each(columns, function (col) {
  988. // });
  989. deduceAxisTypes();
  990. if ((!options.columnTypes || options.columnTypes.length === 0) &&
  991. dataTypes.length &&
  992. dataTypes[0].length &&
  993. dataTypes[0][1] === 'date' &&
  994. !options.dateFormat) {
  995. options.dateFormat = deduceDateFormat(columns[0]);
  996. }
  997. // each(lines, function (line, rowNo) {
  998. // var trimmed = self.trim(line),
  999. // isComment = trimmed.indexOf('#') === 0,
  1000. // isBlank = trimmed === '',
  1001. // items;
  1002. // if (
  1003. // rowNo >= startRow &&
  1004. // rowNo <= endRow &&
  1005. // !isComment && !isBlank
  1006. // ) {
  1007. // items = line.split(itemDelimiter);
  1008. // each(items, function (item, colNo) {
  1009. // if (colNo >= startColumn && colNo <= endColumn) {
  1010. // if (!columns[colNo - startColumn]) {
  1011. // columns[colNo - startColumn] = [];
  1012. // }
  1013. // columns[colNo - startColumn][activeRowNo] = item;
  1014. // }
  1015. // });
  1016. // activeRowNo += 1;
  1017. // }
  1018. // });
  1019. //
  1020. this.dataFound();
  1021. }
  1022. return columns;
  1023. },
  1024. /**
  1025. * Parse a HTML table
  1026. */
  1027. parseTable: function () {
  1028. var options = this.options,
  1029. table = options.table,
  1030. columns = this.columns,
  1031. startRow = options.startRow || 0,
  1032. endRow = options.endRow || Number.MAX_VALUE,
  1033. startColumn = options.startColumn || 0,
  1034. endColumn = options.endColumn || Number.MAX_VALUE;
  1035. if (table) {
  1036. if (typeof table === 'string') {
  1037. table = doc.getElementById(table);
  1038. }
  1039. each(table.getElementsByTagName('tr'), function (tr, rowNo) {
  1040. if (rowNo >= startRow && rowNo <= endRow) {
  1041. each(tr.children, function (item, colNo) {
  1042. if (
  1043. (item.tagName === 'TD' || item.tagName === 'TH') &&
  1044. colNo >= startColumn &&
  1045. colNo <= endColumn
  1046. ) {
  1047. if (!columns[colNo - startColumn]) {
  1048. columns[colNo - startColumn] = [];
  1049. }
  1050. columns[colNo - startColumn][rowNo - startRow] =
  1051. item.innerHTML;
  1052. }
  1053. });
  1054. }
  1055. });
  1056. this.dataFound(); // continue
  1057. }
  1058. return columns;
  1059. },
  1060. /**
  1061. * Fetch or refetch live data
  1062. */
  1063. fetchLiveData: function () {
  1064. var chart = this.chart,
  1065. options = this.options,
  1066. maxRetries = 3,
  1067. currentRetries = 0,
  1068. pollingEnabled = options.enablePolling,
  1069. updateIntervalMs = (options.dataRefreshRate || 2) * 1000,
  1070. originalOptions = merge(options);
  1071. if (!options ||
  1072. (!options.csvURL && !options.rowsURL && !options.columnsURL)
  1073. ) {
  1074. return false;
  1075. }
  1076. // Do not allow polling more than once a second
  1077. if (updateIntervalMs < 1000) {
  1078. updateIntervalMs = 1000;
  1079. }
  1080. delete options.csvURL;
  1081. delete options.rowsURL;
  1082. delete options.columnsURL;
  1083. function performFetch(initialFetch) {
  1084. // Helper function for doing the data fetch + polling
  1085. function request(url, done, tp) {
  1086. if (!url || url.indexOf('http') !== 0) {
  1087. if (url && options.error) {
  1088. options.error('Invalid URL');
  1089. }
  1090. return false;
  1091. }
  1092. if (initialFetch) {
  1093. clearTimeout(chart.liveDataTimeout);
  1094. chart.liveDataURL = url;
  1095. }
  1096. function poll() {
  1097. // Poll
  1098. if (pollingEnabled && chart.liveDataURL === url) {
  1099. // We need to stop doing this if the URL has changed
  1100. chart.liveDataTimeout =
  1101. setTimeout(performFetch, updateIntervalMs);
  1102. }
  1103. }
  1104. Highcharts.ajax({
  1105. url: url,
  1106. dataType: tp || 'json',
  1107. success: function (res) {
  1108. if (chart && chart.series) {
  1109. done(res);
  1110. }
  1111. poll();
  1112. },
  1113. error: function (xhr, text) {
  1114. if (++currentRetries < maxRetries) {
  1115. poll();
  1116. }
  1117. return options.error && options.error(text, xhr);
  1118. }
  1119. });
  1120. return true;
  1121. }
  1122. if (!request(originalOptions.csvURL, function (res) {
  1123. chart.update({
  1124. data: {
  1125. csv: res
  1126. }
  1127. });
  1128. }, 'text')) {
  1129. if (!request(originalOptions.rowsURL, function (res) {
  1130. chart.update({
  1131. data: {
  1132. rows: res
  1133. }
  1134. });
  1135. })) {
  1136. request(originalOptions.columnsURL, function (res) {
  1137. chart.update({
  1138. data: {
  1139. columns: res
  1140. }
  1141. });
  1142. });
  1143. }
  1144. }
  1145. }
  1146. performFetch(true);
  1147. return (options &&
  1148. (options.csvURL || options.rowsURL || options.columnsURL)
  1149. );
  1150. },
  1151. /**
  1152. * Parse a Google spreadsheet.
  1153. */
  1154. parseGoogleSpreadsheet: function () {
  1155. var options = this.options,
  1156. googleSpreadsheetKey = options.googleSpreadsheetKey,
  1157. chart = this.chart,
  1158. // use sheet 1 as the default rather than od6
  1159. // as the latter sometimes cause issues (it looks like it can
  1160. // be renamed in some cases, ref. a fogbugz case).
  1161. worksheet = options.googleSpreadsheetWorksheet || 1,
  1162. startRow = options.startRow || 0,
  1163. endRow = options.endRow || Number.MAX_VALUE,
  1164. startColumn = options.startColumn || 0,
  1165. endColumn = options.endColumn || Number.MAX_VALUE,
  1166. refreshRate = (options.dataRefreshRate || 2) * 1000;
  1167. if (refreshRate < 4000) {
  1168. refreshRate = 4000;
  1169. }
  1170. /*
  1171. * Fetch the actual spreadsheet using XMLHttpRequest
  1172. */
  1173. function fetchSheet(fn) {
  1174. var url = [
  1175. 'https://spreadsheets.google.com/feeds/cells',
  1176. googleSpreadsheetKey,
  1177. worksheet,
  1178. 'public/values?alt=json'
  1179. ].join('/');
  1180. Highcharts.ajax({
  1181. url: url,
  1182. dataType: 'json',
  1183. success: function (json) {
  1184. fn(json);
  1185. if (options.enablePolling) {
  1186. setTimeout(function () {
  1187. fetchSheet(fn);
  1188. }, options.dataRefreshRate);
  1189. }
  1190. },
  1191. error: function (xhr, text) {
  1192. return options.error && options.error(text, xhr);
  1193. }
  1194. });
  1195. }
  1196. if (googleSpreadsheetKey) {
  1197. delete options.googleSpreadsheetKey;
  1198. fetchSheet(function (json) {
  1199. // Prepare the data from the spreadsheat
  1200. var columns = [],
  1201. cells = json.feed.entry,
  1202. cell,
  1203. cellCount = (cells || []).length,
  1204. colCount = 0,
  1205. rowCount = 0,
  1206. val,
  1207. gr,
  1208. gc,
  1209. cellInner,
  1210. i;
  1211. if (!cells || cells.length === 0) {
  1212. return false;
  1213. }
  1214. // First, find the total number of columns and rows that
  1215. // are actually filled with data
  1216. for (i = 0; i < cellCount; i++) {
  1217. cell = cells[i];
  1218. colCount = Math.max(colCount, cell.gs$cell.col);
  1219. rowCount = Math.max(rowCount, cell.gs$cell.row);
  1220. }
  1221. // Set up arrays containing the column data
  1222. for (i = 0; i < colCount; i++) {
  1223. if (i >= startColumn && i <= endColumn) {
  1224. // Create new columns with the length of either
  1225. // end-start or rowCount
  1226. columns[i - startColumn] = [];
  1227. }
  1228. }
  1229. // Loop over the cells and assign the value to the right
  1230. // place in the column arrays
  1231. for (i = 0; i < cellCount; i++) {
  1232. cell = cells[i];
  1233. gr = cell.gs$cell.row - 1; // rows start at 1
  1234. gc = cell.gs$cell.col - 1; // columns start at 1
  1235. // If both row and col falls inside start and end set the
  1236. // transposed cell value in the newly created columns
  1237. if (gc >= startColumn && gc <= endColumn &&
  1238. gr >= startRow && gr <= endRow) {
  1239. cellInner = cell.gs$cell || cell.content;
  1240. val = null;
  1241. if (cellInner.numericValue) {
  1242. if (cellInner.$t.indexOf('/') >= 0 ||
  1243. cellInner.$t.indexOf('-') >= 0) {
  1244. // This is a date - for future reference.
  1245. val = cellInner.$t;
  1246. } else if (cellInner.$t.indexOf('%') > 0) {
  1247. // Percentage
  1248. val = parseFloat(cellInner.numericValue) * 100;
  1249. } else {
  1250. val = parseFloat(cellInner.numericValue);
  1251. }
  1252. } else if (cellInner.$t && cellInner.$t.length) {
  1253. val = cellInner.$t;
  1254. }
  1255. columns[gc - startColumn][gr - startRow] = val;
  1256. }
  1257. }
  1258. // Insert null for empty spreadsheet cells (#5298)
  1259. each(columns, function (column) {
  1260. for (i = 0; i < column.length; i++) {
  1261. if (column[i] === undefined) {
  1262. column[i] = null;
  1263. }
  1264. }
  1265. });
  1266. if (chart && chart.series) {
  1267. chart.update({
  1268. data: {
  1269. columns: columns
  1270. }
  1271. });
  1272. }
  1273. });
  1274. }
  1275. // This is an intermediate fetch, so always return false.
  1276. return false;
  1277. },
  1278. /**
  1279. * Trim a string from whitespace
  1280. */
  1281. trim: function (str, inside) {
  1282. if (typeof str === 'string') {
  1283. str = str.replace(/^\s+|\s+$/g, '');
  1284. // Clear white space insdie the string, like thousands separators
  1285. if (inside && /^[0-9\s]+$/.test(str)) {
  1286. str = str.replace(/\s/g, '');
  1287. }
  1288. if (this.decimalRegex) {
  1289. str = str.replace(this.decimalRegex, '$1.$2');
  1290. }
  1291. }
  1292. return str;
  1293. },
  1294. /**
  1295. * Parse numeric cells in to number types and date types in to true dates.
  1296. */
  1297. parseTypes: function () {
  1298. var columns = this.columns,
  1299. col = columns.length;
  1300. while (col--) {
  1301. this.parseColumn(columns[col], col);
  1302. }
  1303. },
  1304. /**
  1305. * Parse a single column. Set properties like .isDatetime and .isNumeric.
  1306. */
  1307. parseColumn: function (column, col) {
  1308. var rawColumns = this.rawColumns,
  1309. columns = this.columns,
  1310. row = column.length,
  1311. val,
  1312. floatVal,
  1313. trimVal,
  1314. trimInsideVal,
  1315. firstRowAsNames = this.firstRowAsNames,
  1316. isXColumn = inArray(col, this.valueCount.xColumns) !== -1,
  1317. dateVal,
  1318. backup = [],
  1319. diff,
  1320. chartOptions = this.chartOptions,
  1321. descending,
  1322. columnTypes = this.options.columnTypes || [],
  1323. columnType = columnTypes[col],
  1324. forceCategory = isXColumn && ((
  1325. chartOptions &&
  1326. chartOptions.xAxis &&
  1327. splat(chartOptions.xAxis)[0].type === 'category'
  1328. ) || columnType === 'string');
  1329. if (!rawColumns[col]) {
  1330. rawColumns[col] = [];
  1331. }
  1332. while (row--) {
  1333. val = backup[row] || column[row];
  1334. trimVal = this.trim(val);
  1335. trimInsideVal = this.trim(val, true);
  1336. floatVal = parseFloat(trimInsideVal);
  1337. // Set it the first time
  1338. if (rawColumns[col][row] === undefined) {
  1339. rawColumns[col][row] = trimVal;
  1340. }
  1341. // Disable number or date parsing by setting the X axis type to
  1342. // category
  1343. if (forceCategory || (row === 0 && firstRowAsNames)) {
  1344. column[row] = '' + trimVal;
  1345. } else if (+trimInsideVal === floatVal) { // is numeric
  1346. column[row] = floatVal;
  1347. // If the number is greater than milliseconds in a year, assume
  1348. // datetime
  1349. if (
  1350. floatVal > 365 * 24 * 3600 * 1000 &&
  1351. columnType !== 'float'
  1352. ) {
  1353. column.isDatetime = true;
  1354. } else {
  1355. column.isNumeric = true;
  1356. }
  1357. if (column[row + 1] !== undefined) {
  1358. descending = floatVal > column[row + 1];
  1359. }
  1360. // String, continue to determine if it is a date string or really a
  1361. // string
  1362. } else {
  1363. if (trimVal && trimVal.length) {
  1364. dateVal = this.parseDate(val);
  1365. }
  1366. // Only allow parsing of dates if this column is an x-column
  1367. if (isXColumn && isNumber(dateVal) && columnType !== 'float') {
  1368. backup[row] = val;
  1369. column[row] = dateVal;
  1370. column.isDatetime = true;
  1371. // Check if the dates are uniformly descending or ascending.
  1372. // If they are not, chances are that they are a different
  1373. // time format, so check for alternative.
  1374. if (column[row + 1] !== undefined) {
  1375. diff = dateVal > column[row + 1];
  1376. if (diff !== descending && descending !== undefined) {
  1377. if (this.alternativeFormat) {
  1378. this.dateFormat = this.alternativeFormat;
  1379. row = column.length;
  1380. this.alternativeFormat =
  1381. this.dateFormats[this.dateFormat]
  1382. .alternative;
  1383. } else {
  1384. column.unsorted = true;
  1385. }
  1386. }
  1387. descending = diff;
  1388. }
  1389. } else { // string
  1390. column[row] = trimVal === '' ? null : trimVal;
  1391. if (row !== 0 && (column.isDatetime || column.isNumeric)) {
  1392. column.mixed = true;
  1393. }
  1394. }
  1395. }
  1396. }
  1397. // If strings are intermixed with numbers or dates in a parsed column,
  1398. // it is an indication that parsing went wrong or the data was not
  1399. // intended to display as numbers or dates and parsing is too
  1400. // aggressive. Fall back to categories. Demonstrated in the
  1401. // highcharts/demo/column-drilldown sample.
  1402. if (isXColumn && column.mixed) {
  1403. columns[col] = rawColumns[col];
  1404. }
  1405. // If the 0 column is date or number and descending, reverse all
  1406. // columns.
  1407. if (isXColumn && descending && this.options.sort) {
  1408. for (col = 0; col < columns.length; col++) {
  1409. columns[col].reverse();
  1410. if (firstRowAsNames) {
  1411. columns[col].unshift(columns[col].pop());
  1412. }
  1413. }
  1414. }
  1415. },
  1416. /**
  1417. * A collection of available date formats, extendable from the outside to
  1418. * support custom date formats.
  1419. */
  1420. dateFormats: {
  1421. 'YYYY/mm/dd': {
  1422. regex: /^([0-9]{4})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{1,2})$/,
  1423. parser: function (match) {
  1424. return Date.UTC(+match[1], match[2] - 1, +match[3]);
  1425. }
  1426. },
  1427. 'dd/mm/YYYY': {
  1428. regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
  1429. parser: function (match) {
  1430. return Date.UTC(+match[3], match[2] - 1, +match[1]);
  1431. },
  1432. alternative: 'mm/dd/YYYY' // different format with the same regex
  1433. },
  1434. 'mm/dd/YYYY': {
  1435. regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
  1436. parser: function (match) {
  1437. return Date.UTC(+match[3], match[1] - 1, +match[2]);
  1438. }
  1439. },
  1440. 'dd/mm/YY': {
  1441. regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
  1442. parser: function (match) {
  1443. var year = +match[3],
  1444. d = new Date()
  1445. ;
  1446. if (year > (d.getFullYear() - 2000)) {
  1447. year += 1900;
  1448. } else {
  1449. year += 2000;
  1450. }
  1451. return Date.UTC(year, match[2] - 1, +match[1]);
  1452. },
  1453. alternative: 'mm/dd/YY' // different format with the same regex
  1454. },
  1455. 'mm/dd/YY': {
  1456. regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
  1457. parser: function (match) {
  1458. return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);
  1459. }
  1460. }
  1461. },
  1462. /**
  1463. * Parse a date and return it as a number. Overridable through
  1464. * `options.parseDate`.
  1465. */
  1466. parseDate: function (val) {
  1467. var parseDate = this.options.parseDate,
  1468. ret,
  1469. key,
  1470. format,
  1471. dateFormat = this.options.dateFormat || this.dateFormat,
  1472. match;
  1473. if (parseDate) {
  1474. ret = parseDate(val);
  1475. } else if (typeof val === 'string') {
  1476. // Auto-detect the date format the first time
  1477. if (!dateFormat) {
  1478. for (key in this.dateFormats) {
  1479. format = this.dateFormats[key];
  1480. match = val.match(format.regex);
  1481. if (match) {
  1482. this.dateFormat = dateFormat = key;
  1483. this.alternativeFormat = format.alternative;
  1484. ret = format.parser(match);
  1485. break;
  1486. }
  1487. }
  1488. // Next time, use the one previously found
  1489. } else {
  1490. format = this.dateFormats[dateFormat];
  1491. if (!format) {
  1492. // The selected format is invalid
  1493. format = this.dateFormats['YYYY/mm/dd'];
  1494. }
  1495. match = val.match(format.regex);
  1496. if (match) {
  1497. ret = format.parser(match);
  1498. }
  1499. }
  1500. // Fall back to Date.parse
  1501. if (!match) {
  1502. match = Date.parse(val);
  1503. // External tools like Date.js and MooTools extend Date object
  1504. // and returns a date.
  1505. if (
  1506. typeof match === 'object' &&
  1507. match !== null &&
  1508. match.getTime
  1509. ) {
  1510. ret = match.getTime() - match.getTimezoneOffset() * 60000;
  1511. // Timestamp
  1512. } else if (isNumber(match)) {
  1513. ret = match - (new Date(match)).getTimezoneOffset() * 60000;
  1514. }
  1515. }
  1516. }
  1517. return ret;
  1518. },
  1519. /**
  1520. * Reorganize rows into columns
  1521. */
  1522. rowsToColumns: function (rows) {
  1523. var row,
  1524. rowsLength,
  1525. col,
  1526. colsLength,
  1527. columns;
  1528. if (rows) {
  1529. columns = [];
  1530. rowsLength = rows.length;
  1531. for (row = 0; row < rowsLength; row++) {
  1532. colsLength = rows[row].length;
  1533. for (col = 0; col < colsLength; col++) {
  1534. if (!columns[col]) {
  1535. columns[col] = [];
  1536. }
  1537. columns[col][row] = rows[row][col];
  1538. }
  1539. }
  1540. }
  1541. return columns;
  1542. },
  1543. /**
  1544. * A hook for working directly on the parsed columns
  1545. */
  1546. parsed: function () {
  1547. if (this.options.parsed) {
  1548. return this.options.parsed.call(this, this.columns);
  1549. }
  1550. },
  1551. getFreeIndexes: function (numberOfColumns, seriesBuilders) {
  1552. var s,
  1553. i,
  1554. freeIndexes = [],
  1555. freeIndexValues = [],
  1556. referencedIndexes;
  1557. // Add all columns as free
  1558. for (i = 0; i < numberOfColumns; i = i + 1) {
  1559. freeIndexes.push(true);
  1560. }
  1561. // Loop all defined builders and remove their referenced columns
  1562. for (s = 0; s < seriesBuilders.length; s = s + 1) {
  1563. referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes();
  1564. for (i = 0; i < referencedIndexes.length; i = i + 1) {
  1565. freeIndexes[referencedIndexes[i]] = false;
  1566. }
  1567. }
  1568. // Collect the values for the free indexes
  1569. for (i = 0; i < freeIndexes.length; i = i + 1) {
  1570. if (freeIndexes[i]) {
  1571. freeIndexValues.push(i);
  1572. }
  1573. }
  1574. return freeIndexValues;
  1575. },
  1576. /**
  1577. * If a complete callback function is provided in the options, interpret the
  1578. * columns into a Highcharts options object.
  1579. */
  1580. complete: function () {
  1581. var columns = this.columns,
  1582. xColumns = [],
  1583. type,
  1584. options = this.options,
  1585. series,
  1586. data,
  1587. i,
  1588. j,
  1589. r,
  1590. seriesIndex,
  1591. chartOptions,
  1592. allSeriesBuilders = [],
  1593. builder,
  1594. freeIndexes,
  1595. typeCol,
  1596. index;
  1597. xColumns.length = columns.length;
  1598. if (options.complete || options.afterComplete) {
  1599. // Get the names and shift the top row
  1600. if (this.firstRowAsNames) {
  1601. for (i = 0; i < columns.length; i++) {
  1602. columns[i].name = columns[i].shift();
  1603. }
  1604. }
  1605. // Use the next columns for series
  1606. series = [];
  1607. freeIndexes = this.getFreeIndexes(
  1608. columns.length,
  1609. this.valueCount.seriesBuilders
  1610. );
  1611. // Populate defined series
  1612. for (
  1613. seriesIndex = 0;
  1614. seriesIndex < this.valueCount.seriesBuilders.length;
  1615. seriesIndex++
  1616. ) {
  1617. builder = this.valueCount.seriesBuilders[seriesIndex];
  1618. // If the builder can be populated with remaining columns, then
  1619. // add it to allBuilders
  1620. if (builder.populateColumns(freeIndexes)) {
  1621. allSeriesBuilders.push(builder);
  1622. }
  1623. }
  1624. // Populate dynamic series
  1625. while (freeIndexes.length > 0) {
  1626. builder = new SeriesBuilder();
  1627. builder.addColumnReader(0, 'x');
  1628. // Mark index as used (not free)
  1629. index = inArray(0, freeIndexes);
  1630. if (index !== -1) {
  1631. freeIndexes.splice(index, 1);
  1632. }
  1633. for (i = 0; i < this.valueCount.global; i++) {
  1634. // Create and add a column reader for the next free column
  1635. // index
  1636. builder.addColumnReader(
  1637. undefined,
  1638. this.valueCount.globalPointArrayMap[i]
  1639. );
  1640. }
  1641. // If the builder can be populated with remaining columns, then
  1642. // add it to allBuilders
  1643. if (builder.populateColumns(freeIndexes)) {
  1644. allSeriesBuilders.push(builder);
  1645. }
  1646. }
  1647. // Get the data-type from the first series x column
  1648. if (
  1649. allSeriesBuilders.length > 0 &&
  1650. allSeriesBuilders[0].readers.length > 0
  1651. ) {
  1652. typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];
  1653. if (typeCol !== undefined) {
  1654. if (typeCol.isDatetime) {
  1655. type = 'datetime';
  1656. } else if (!typeCol.isNumeric) {
  1657. type = 'category';
  1658. }
  1659. }
  1660. }
  1661. // Axis type is category, then the "x" column should be called
  1662. // "name"
  1663. if (type === 'category') {
  1664. for (
  1665. seriesIndex = 0;
  1666. seriesIndex < allSeriesBuilders.length;
  1667. seriesIndex++
  1668. ) {
  1669. builder = allSeriesBuilders[seriesIndex];
  1670. for (r = 0; r < builder.readers.length; r++) {
  1671. if (builder.readers[r].configName === 'x') {
  1672. builder.readers[r].configName = 'name';
  1673. }
  1674. }
  1675. }
  1676. }
  1677. // Read data for all builders
  1678. for (
  1679. seriesIndex = 0;
  1680. seriesIndex < allSeriesBuilders.length;
  1681. seriesIndex++
  1682. ) {
  1683. builder = allSeriesBuilders[seriesIndex];
  1684. // Iterate down the cells of each column and add data to the
  1685. // series
  1686. data = [];
  1687. for (j = 0; j < columns[0].length; j++) {
  1688. data[j] = builder.read(columns, j);
  1689. }
  1690. // Add the series
  1691. series[seriesIndex] = {
  1692. data: data
  1693. };
  1694. if (builder.name) {
  1695. series[seriesIndex].name = builder.name;
  1696. }
  1697. if (type === 'category') {
  1698. series[seriesIndex].turboThreshold = 0;
  1699. }
  1700. }
  1701. // Do the callback
  1702. chartOptions = {
  1703. series: series
  1704. };
  1705. if (type) {
  1706. chartOptions.xAxis = {
  1707. type: type
  1708. };
  1709. if (type === 'category') {
  1710. chartOptions.xAxis.uniqueNames = false;
  1711. }
  1712. }
  1713. if (options.complete) {
  1714. options.complete(chartOptions);
  1715. }
  1716. // The afterComplete hook is used internally to avoid conflict with
  1717. // the externally available complete option.
  1718. if (options.afterComplete) {
  1719. options.afterComplete(chartOptions);
  1720. }
  1721. }
  1722. },
  1723. update: function (options, redraw) {
  1724. var chart = this.chart;
  1725. if (options) {
  1726. // Set the complete handler
  1727. options.afterComplete = function (dataOptions) {
  1728. // Avoid setting axis options unless the type changes. Running
  1729. // Axis.update will cause the whole structure to be destroyed
  1730. // and rebuilt, and animation is lost.
  1731. if (
  1732. dataOptions.xAxis &&
  1733. chart.xAxis[0] &&
  1734. dataOptions.xAxis.type === chart.xAxis[0].options.type
  1735. ) {
  1736. delete dataOptions.xAxis;
  1737. }
  1738. chart.update(dataOptions, redraw, true);
  1739. };
  1740. // Apply it
  1741. merge(true, this.options, options);
  1742. this.init(this.options);
  1743. }
  1744. }
  1745. });
  1746. // Register the Data prototype and data function on Highcharts
  1747. Highcharts.Data = Data;
  1748. Highcharts.data = function (options, chartOptions) {
  1749. return new Data(options, chartOptions);
  1750. };
  1751. // Extend Chart.init so that the Chart constructor accepts a new configuration
  1752. // option group, data.
  1753. addEvent(
  1754. Chart,
  1755. 'init',
  1756. function (e) {
  1757. var chart = this,
  1758. userOptions = e.args[0],
  1759. callback = e.args[1];
  1760. if (userOptions && userOptions.data && !chart.hasDataDef) {
  1761. chart.hasDataDef = true;
  1762. chart.data = new Data(Highcharts.extend(userOptions.data, {
  1763. afterComplete: function (dataOptions) {
  1764. var i, series;
  1765. // Merge series configs
  1766. if (userOptions.hasOwnProperty('series')) {
  1767. if (typeof userOptions.series === 'object') {
  1768. i = Math.max(
  1769. userOptions.series.length,
  1770. dataOptions && dataOptions.series ?
  1771. dataOptions.series.length :
  1772. 0
  1773. );
  1774. while (i--) {
  1775. series = userOptions.series[i] || {};
  1776. userOptions.series[i] = merge(
  1777. series,
  1778. dataOptions && dataOptions.series ?
  1779. dataOptions.series[i] :
  1780. {}
  1781. );
  1782. }
  1783. } else { // Allow merging in dataOptions.series (#2856)
  1784. delete userOptions.series;
  1785. }
  1786. }
  1787. // Do the merge
  1788. userOptions = merge(dataOptions, userOptions);
  1789. // Run chart.init again
  1790. chart.init(userOptions, callback);
  1791. }
  1792. }), userOptions, chart);
  1793. e.preventDefault();
  1794. }
  1795. }
  1796. );
  1797. /**
  1798. * Creates a new SeriesBuilder. A SeriesBuilder consists of a number
  1799. * of ColumnReaders that reads columns and give them a name.
  1800. * Ex: A series builder can be constructed to read column 3 as 'x' and
  1801. * column 7 and 8 as 'y1' and 'y2'.
  1802. * The output would then be points/rows of the form {x: 11, y1: 22, y2: 33}
  1803. *
  1804. * The name of the builder is taken from the second column. In the above
  1805. * example it would be the column with index 7.
  1806. * @constructor
  1807. */
  1808. SeriesBuilder = function () {
  1809. this.readers = [];
  1810. this.pointIsArray = true;
  1811. };
  1812. /**
  1813. * Populates readers with column indexes. A reader can be added without
  1814. * a specific index and for those readers the index is taken sequentially
  1815. * from the free columns (this is handled by the ColumnCursor instance).
  1816. * @returns {boolean}
  1817. */
  1818. SeriesBuilder.prototype.populateColumns = function (freeIndexes) {
  1819. var builder = this,
  1820. enoughColumns = true;
  1821. // Loop each reader and give it an index if its missing.
  1822. // The freeIndexes.shift() will return undefined if there
  1823. // are no more columns.
  1824. each(builder.readers, function (reader) {
  1825. if (reader.columnIndex === undefined) {
  1826. reader.columnIndex = freeIndexes.shift();
  1827. }
  1828. });
  1829. // Now, all readers should have columns mapped. If not
  1830. // then return false to signal that this series should
  1831. // not be added.
  1832. each(builder.readers, function (reader) {
  1833. if (reader.columnIndex === undefined) {
  1834. enoughColumns = false;
  1835. }
  1836. });
  1837. return enoughColumns;
  1838. };
  1839. /**
  1840. * Reads a row from the dataset and returns a point or array depending
  1841. * on the names of the readers.
  1842. * @param columns
  1843. * @param rowIndex
  1844. * @returns {Array | Object}
  1845. */
  1846. SeriesBuilder.prototype.read = function (columns, rowIndex) {
  1847. var builder = this,
  1848. pointIsArray = builder.pointIsArray,
  1849. point = pointIsArray ? [] : {},
  1850. columnIndexes;
  1851. // Loop each reader and ask it to read its value.
  1852. // Then, build an array or point based on the readers names.
  1853. each(builder.readers, function (reader) {
  1854. var value = columns[reader.columnIndex][rowIndex];
  1855. if (pointIsArray) {
  1856. point.push(value);
  1857. } else {
  1858. if (reader.configName.indexOf('.') > 0) {
  1859. // Handle nested property names
  1860. Highcharts.Point.prototype.setNestedProperty(
  1861. point, value, reader.configName
  1862. );
  1863. } else {
  1864. point[reader.configName] = value;
  1865. }
  1866. }
  1867. });
  1868. // The name comes from the first column (excluding the x column)
  1869. if (this.name === undefined && builder.readers.length >= 2) {
  1870. columnIndexes = builder.getReferencedColumnIndexes();
  1871. if (columnIndexes.length >= 2) {
  1872. // remove the first one (x col)
  1873. columnIndexes.shift();
  1874. // Sort the remaining
  1875. columnIndexes.sort();
  1876. // Now use the lowest index as name column
  1877. this.name = columns[columnIndexes.shift()].name;
  1878. }
  1879. }
  1880. return point;
  1881. };
  1882. /**
  1883. * Creates and adds ColumnReader from the given columnIndex and configName.
  1884. * ColumnIndex can be undefined and in that case the reader will be given
  1885. * an index when columns are populated.
  1886. * @param columnIndex {Number | undefined}
  1887. * @param configName
  1888. */
  1889. SeriesBuilder.prototype.addColumnReader = function (columnIndex, configName) {
  1890. this.readers.push({
  1891. columnIndex: columnIndex,
  1892. configName: configName
  1893. });
  1894. if (
  1895. !(configName === 'x' || configName === 'y' || configName === undefined)
  1896. ) {
  1897. this.pointIsArray = false;
  1898. }
  1899. };
  1900. /**
  1901. * Returns an array of column indexes that the builder will use when
  1902. * reading data.
  1903. * @returns {Array}
  1904. */
  1905. SeriesBuilder.prototype.getReferencedColumnIndexes = function () {
  1906. var i,
  1907. referencedColumnIndexes = [],
  1908. columnReader;
  1909. for (i = 0; i < this.readers.length; i = i + 1) {
  1910. columnReader = this.readers[i];
  1911. if (columnReader.columnIndex !== undefined) {
  1912. referencedColumnIndexes.push(columnReader.columnIndex);
  1913. }
  1914. }
  1915. return referencedColumnIndexes;
  1916. };
  1917. /**
  1918. * Returns true if the builder has a reader for the given configName.
  1919. * @param configName
  1920. * @returns {boolean}
  1921. */
  1922. SeriesBuilder.prototype.hasReader = function (configName) {
  1923. var i, columnReader;
  1924. for (i = 0; i < this.readers.length; i = i + 1) {
  1925. columnReader = this.readers[i];
  1926. if (columnReader.configName === configName) {
  1927. return true;
  1928. }
  1929. }
  1930. // Else return undefined
  1931. };
  1932. }(Highcharts));
  1933. }));