data.src.js 82 KB

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