export-data.src.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234
  1. /**
  2. * @license Highcharts JS v7.0.2 (2019-01-17)
  3. * Exporting module
  4. *
  5. * (c) 2010-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. * Mixin for downloading content in the browser
  135. *
  136. * (c) 2015-2019 Oystein Moseng
  137. *
  138. * License: www.highcharts.com/license
  139. */
  140. var win = Highcharts.win,
  141. nav = win.navigator,
  142. doc = win.document,
  143. domurl = win.URL || win.webkitURL || win,
  144. isEdgeBrowser = /Edge\/\d+/.test(nav.userAgent);
  145. // Convert base64 dataURL to Blob if supported, otherwise returns undefined
  146. Highcharts.dataURLtoBlob = function (dataURL) {
  147. var parts = dataURL.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/);
  148. if (
  149. parts &&
  150. parts.length > 3 &&
  151. win.atob &&
  152. win.ArrayBuffer &&
  153. win.Uint8Array &&
  154. win.Blob &&
  155. domurl.createObjectURL
  156. ) {
  157. // Try to convert data URL to Blob
  158. var binStr = win.atob(parts[3]),
  159. buf = new win.ArrayBuffer(binStr.length),
  160. binary = new win.Uint8Array(buf),
  161. blob;
  162. for (var i = 0; i < binary.length; ++i) {
  163. binary[i] = binStr.charCodeAt(i);
  164. }
  165. blob = new win.Blob([binary], { 'type': parts[1] });
  166. return domurl.createObjectURL(blob);
  167. }
  168. };
  169. /**
  170. * Download a data URL in the browser. Can also take a blob as first param.
  171. *
  172. * @private
  173. * @function Highcharts.downloadURL
  174. *
  175. * @param {string|object} dataURL
  176. * The dataURL/Blob to download
  177. *
  178. * @param {string} filename
  179. * The name of the resulting file (w/extension)
  180. */
  181. Highcharts.downloadURL = function (dataURL, filename) {
  182. var a = doc.createElement('a'),
  183. windowRef;
  184. // IE specific blob implementation
  185. // Don't use for normal dataURLs
  186. if (
  187. typeof dataURL !== 'string' &&
  188. !(dataURL instanceof String) &&
  189. nav.msSaveOrOpenBlob
  190. ) {
  191. nav.msSaveOrOpenBlob(dataURL, filename);
  192. return;
  193. }
  194. // Some browsers have limitations for data URL lengths. Try to convert to
  195. // Blob or fall back. Edge always needs that blob.
  196. if (isEdgeBrowser || dataURL.length > 2000000) {
  197. dataURL = Highcharts.dataURLtoBlob(dataURL);
  198. if (!dataURL) {
  199. throw new Error('Failed to convert to blob');
  200. }
  201. }
  202. // Try HTML5 download attr if supported
  203. if (a.download !== undefined) {
  204. a.href = dataURL;
  205. a.download = filename; // HTML5 download attribute
  206. doc.body.appendChild(a);
  207. a.click();
  208. doc.body.removeChild(a);
  209. } else {
  210. // No download attr, just opening data URI
  211. try {
  212. windowRef = win.open(dataURL, 'chart');
  213. if (windowRef === undefined || windowRef === null) {
  214. throw new Error('Failed to open window');
  215. }
  216. } catch (e) {
  217. // window.open failed, trying location.href
  218. win.location.href = dataURL;
  219. }
  220. }
  221. };
  222. }(Highcharts));
  223. (function (Highcharts) {
  224. /**
  225. * Experimental data export module for Highcharts
  226. *
  227. * (c) 2010-2019 Torstein Honsi
  228. *
  229. * License: www.highcharts.com/license
  230. */
  231. // @todo
  232. // - Set up systematic tests for all series types, paired with tests of the data
  233. // module importing the same data.
  234. var defined = Highcharts.defined,
  235. pick = Highcharts.pick,
  236. win = Highcharts.win,
  237. doc = win.document,
  238. seriesTypes = Highcharts.seriesTypes,
  239. downloadURL = Highcharts.downloadURL;
  240. // Can we add this to utils? Also used in screen-reader.js
  241. /**
  242. * HTML encode some characters vulnerable for XSS.
  243. * @param {string} html The input string
  244. * @return {string} The excaped string
  245. */
  246. function htmlencode(html) {
  247. return html
  248. .replace(/&/g, '&amp;')
  249. .replace(/</g, '&lt;')
  250. .replace(/>/g, '&gt;')
  251. .replace(/"/g, '&quot;')
  252. .replace(/'/g, '&#x27;')
  253. .replace(/\//g, '&#x2F;');
  254. }
  255. Highcharts.setOptions({
  256. /**
  257. * @optionparent exporting
  258. */
  259. exporting: {
  260. /**
  261. * Export-data module required. Caption for the data table. Same as
  262. * chart title by default. Set to `false` to disable.
  263. *
  264. * @sample highcharts/export-data/multilevel-table
  265. * Multiple table headers
  266. *
  267. * @type {boolean|string}
  268. * @since 6.0.4
  269. * @apioption exporting.tableCaption
  270. */
  271. /**
  272. * Options for exporting data to CSV or ExCel, or displaying the data
  273. * in a HTML table or a JavaScript structure. Requires the
  274. * `export-data.js` module. This module adds data export options to the
  275. * export menu and provides functions like `Chart.getCSV`,
  276. * `Chart.getTable`, `Chart.getDataRows` and `Chart.viewData`.
  277. *
  278. * The XLS converter is limited and only creates a HTML string that is
  279. * passed for download, which works but creates a warning before
  280. * opening. The workaround for this is to use a third party XLSX
  281. * converter, as demonstrated in the sample below.
  282. *
  283. * @sample highcharts/export-data/categorized/ Categorized data
  284. * @sample highcharts/export-data/stock-timeaxis/ Highstock time axis
  285. * @sample highcharts/export-data/xlsx/
  286. * Using a third party XLSX converter
  287. *
  288. * @since 6.0.0
  289. */
  290. csv: {
  291. /**
  292. * Formatter callback for the column headers. Parameters are:
  293. * - `item` - The series or axis object)
  294. * - `key` - The point key, for example y or z
  295. * - `keyLength` - The amount of value keys for this item, for
  296. * example a range series has the keys `low` and `high` so the
  297. * key length is 2.
  298. *
  299. * If [useMultiLevelHeaders](#exporting.useMultiLevelHeaders) is
  300. * true, columnHeaderFormatter by default returns an object with
  301. * columnTitle and topLevelColumnTitle for each key. Columns with
  302. * the same topLevelColumnTitle have their titles merged into a
  303. * single cell with colspan for table/Excel export.
  304. *
  305. * If `useMultiLevelHeaders` is false, or for CSV export, it returns
  306. * the series name, followed by the key if there is more than one
  307. * key.
  308. *
  309. * For the axis it returns the axis title or "Category" or
  310. * "DateTime" by default.
  311. *
  312. * Return `false` to use Highcharts' proposed header.
  313. *
  314. * @sample highcharts/export-data/multilevel-table
  315. * Multiple table headers
  316. *
  317. * @type {Function|null}
  318. */
  319. columnHeaderFormatter: null,
  320. /**
  321. * Which date format to use for exported dates on a datetime X axis.
  322. * See `Highcharts.dateFormat`.
  323. */
  324. dateFormat: '%Y-%m-%d %H:%M:%S',
  325. /**
  326. * Which decimal point to use for exported CSV. Defaults to the same
  327. * as the browser locale, typically `.` (English) or `,` (German,
  328. * French etc).
  329. *
  330. * @type {string|null}
  331. * @since 6.0.4
  332. */
  333. decimalPoint: null,
  334. /**
  335. * The item delimiter in the exported data. Use `;` for direct
  336. * exporting to Excel. Defaults to a best guess based on the browser
  337. * locale. If the locale _decimal point_ is `,`, the `itemDelimiter`
  338. * defaults to `;`, otherwise the `itemDelimiter` defaults to `,`.
  339. *
  340. * @type {string|null}
  341. */
  342. itemDelimiter: null,
  343. /**
  344. * The line delimiter in the exported data, defaults to a newline.
  345. */
  346. lineDelimiter: '\n'
  347. },
  348. /**
  349. * Export-data module required. Show a HTML table below the chart with
  350. * the chart's current data.
  351. *
  352. * @sample highcharts/export-data/showtable/
  353. * Show the table
  354. * @sample highcharts/studies/exporting-table-html
  355. * Experiment with putting the table inside the subtitle to
  356. * allow exporting it.
  357. *
  358. * @since 6.0.0
  359. */
  360. showTable: false,
  361. /**
  362. * Export-data module required. Use multi level headers in data table.
  363. * If [csv.columnHeaderFormatter](#exporting.csv.columnHeaderFormatter)
  364. * is defined, it has to return objects in order for multi level headers
  365. * to work.
  366. *
  367. * @sample highcharts/export-data/multilevel-table
  368. * Multiple table headers
  369. *
  370. * @since 6.0.4
  371. */
  372. useMultiLevelHeaders: true,
  373. /**
  374. * Export-data module required. If using multi level table headers, use
  375. * rowspans for headers that have only one level.
  376. *
  377. * @sample highcharts/export-data/multilevel-table
  378. * Multiple table headers
  379. *
  380. * @since 6.0.4
  381. */
  382. useRowspanHeaders: true
  383. },
  384. /**
  385. * @optionparent lang
  386. */
  387. lang: {
  388. /**
  389. * Export-data module only. The text for the menu item.
  390. *
  391. * @since 6.0.0
  392. */
  393. downloadCSV: 'Download CSV',
  394. /**
  395. * Export-data module only. The text for the menu item.
  396. *
  397. * @since 6.0.0
  398. */
  399. downloadXLS: 'Download XLS',
  400. /**
  401. * Export-data module only. The text for the menu item.
  402. *
  403. * @since 6.1.0
  404. */
  405. openInCloud: 'Open in Highcharts Cloud',
  406. /**
  407. * Export-data module only. The text for the menu item.
  408. *
  409. * @since 6.0.0
  410. */
  411. viewData: 'View data table'
  412. }
  413. });
  414. // Add an event listener to handle the showTable option
  415. Highcharts.addEvent(Highcharts.Chart, 'render', function () {
  416. if (
  417. this.options &&
  418. this.options.exporting &&
  419. this.options.exporting.showTable
  420. ) {
  421. this.viewData();
  422. }
  423. });
  424. /**
  425. * Set up key-to-axis bindings. This is used when the Y axis is datetime or
  426. * categorized. For example in an arearange series, the low and high values
  427. * should be formatted according to the Y axis type, and in order to link them
  428. * we need this map.
  429. *
  430. * @private
  431. * @function Highcharts.Chart#setUpKeyToAxis
  432. */
  433. Highcharts.Chart.prototype.setUpKeyToAxis = function () {
  434. if (seriesTypes.arearange) {
  435. seriesTypes.arearange.prototype.keyToAxis = {
  436. low: 'y',
  437. high: 'y'
  438. };
  439. }
  440. if (seriesTypes.gantt) {
  441. seriesTypes.gantt.prototype.keyToAxis = {
  442. start: 'x',
  443. end: 'x'
  444. };
  445. }
  446. };
  447. /**
  448. * Export-data module required. Returns a two-dimensional array containing the
  449. * current chart data.
  450. *
  451. * @function Highcharts.Chart#getDataRows
  452. *
  453. * @param {boolean} multiLevelHeaders
  454. * Use multilevel headers for the rows by default. Adds an extra row with
  455. * top level headers. If a custom columnHeaderFormatter is defined, this
  456. * can override the behavior.
  457. *
  458. * @return {Array<Array<number|string>>}
  459. * The current chart data
  460. */
  461. Highcharts.Chart.prototype.getDataRows = function (multiLevelHeaders) {
  462. var time = this.time,
  463. csvOptions = (this.options.exporting && this.options.exporting.csv) ||
  464. {},
  465. xAxis,
  466. xAxes = this.xAxis,
  467. rows = {},
  468. rowArr = [],
  469. dataRows,
  470. topLevelColumnTitles = [],
  471. columnTitles = [],
  472. columnTitleObj,
  473. i,
  474. x,
  475. xTitle,
  476. // Options
  477. columnHeaderFormatter = function (item, key, keyLength) {
  478. if (csvOptions.columnHeaderFormatter) {
  479. var s = csvOptions.columnHeaderFormatter(item, key, keyLength);
  480. if (s !== false) {
  481. return s;
  482. }
  483. }
  484. if (!item) {
  485. return 'Category';
  486. }
  487. if (item instanceof Highcharts.Axis) {
  488. return (item.options.title && item.options.title.text) ||
  489. (item.isDatetimeAxis ? 'DateTime' : 'Category');
  490. }
  491. if (multiLevelHeaders) {
  492. return {
  493. columnTitle: keyLength > 1 ? key : item.name,
  494. topLevelColumnTitle: item.name
  495. };
  496. }
  497. return item.name + (keyLength > 1 ? ' (' + key + ')' : '');
  498. },
  499. xAxisIndices = [];
  500. // Loop the series and index values
  501. i = 0;
  502. this.setUpKeyToAxis();
  503. this.series.forEach(function (series) {
  504. var keys = series.options.keys,
  505. pointArrayMap = keys || series.pointArrayMap || ['y'],
  506. valueCount = pointArrayMap.length,
  507. xTaken = !series.requireSorting && {},
  508. categoryMap = {},
  509. datetimeValueAxisMap = {},
  510. xAxisIndex = xAxes.indexOf(series.xAxis),
  511. mockSeries,
  512. j;
  513. // Map the categories for value axes
  514. pointArrayMap.forEach(function (prop) {
  515. var axisName = (
  516. (series.keyToAxis && series.keyToAxis[prop]) ||
  517. prop
  518. ) + 'Axis';
  519. categoryMap[prop] = (
  520. series[axisName] &&
  521. series[axisName].categories
  522. ) || [];
  523. datetimeValueAxisMap[prop] = (
  524. series[axisName] &&
  525. series[axisName].isDatetimeAxis
  526. );
  527. });
  528. if (
  529. series.options.includeInCSVExport !== false &&
  530. !series.options.isInternal &&
  531. series.visible !== false // #55
  532. ) {
  533. // Build a lookup for X axis index and the position of the first
  534. // series that belongs to that X axis. Includes -1 for non-axis
  535. // series types like pies.
  536. if (!Highcharts.find(xAxisIndices, function (index) {
  537. return index[0] === xAxisIndex;
  538. })) {
  539. xAxisIndices.push([xAxisIndex, i]);
  540. }
  541. // Compute the column headers and top level headers, usually the
  542. // same as series names
  543. j = 0;
  544. while (j < valueCount) {
  545. columnTitleObj = columnHeaderFormatter(
  546. series,
  547. pointArrayMap[j],
  548. pointArrayMap.length
  549. );
  550. columnTitles.push(
  551. columnTitleObj.columnTitle || columnTitleObj
  552. );
  553. if (multiLevelHeaders) {
  554. topLevelColumnTitles.push(
  555. columnTitleObj.topLevelColumnTitle || columnTitleObj
  556. );
  557. }
  558. j++;
  559. }
  560. mockSeries = {
  561. chart: series.chart,
  562. autoIncrement: series.autoIncrement,
  563. options: series.options,
  564. pointArrayMap: series.pointArrayMap
  565. };
  566. // Export directly from options.data because we need the uncropped
  567. // data (#7913), and we need to support Boost (#7026).
  568. series.options.data.forEach(function eachData(options, pIdx) {
  569. var key,
  570. prop,
  571. val,
  572. name,
  573. point;
  574. point = { series: mockSeries };
  575. series.pointClass.prototype.applyOptions.apply(
  576. point,
  577. [options]
  578. );
  579. key = point.x;
  580. name = series.data[pIdx] && series.data[pIdx].name;
  581. if (xTaken) {
  582. if (xTaken[key]) {
  583. key += '|' + pIdx;
  584. }
  585. xTaken[key] = true;
  586. }
  587. j = 0;
  588. // Pies, funnels, geo maps etc. use point name in X row
  589. if (!series.xAxis || series.exportKey === 'name') {
  590. key = name;
  591. }
  592. if (!rows[key]) {
  593. // Generate the row
  594. rows[key] = [];
  595. // Contain the X values from one or more X axes
  596. rows[key].xValues = [];
  597. }
  598. rows[key].x = point.x;
  599. rows[key].name = name;
  600. rows[key].xValues[xAxisIndex] = point.x;
  601. while (j < valueCount) {
  602. prop = pointArrayMap[j]; // y, z etc
  603. val = point[prop];
  604. rows[key][i + j] = pick(
  605. categoryMap[prop][val], // Y axis category if present
  606. datetimeValueAxisMap[prop] ?
  607. time.dateFormat(csvOptions.dateFormat, val) :
  608. null,
  609. val
  610. );
  611. j++;
  612. }
  613. });
  614. i = i + j;
  615. }
  616. });
  617. // Make a sortable array
  618. for (x in rows) {
  619. if (rows.hasOwnProperty(x)) {
  620. rowArr.push(rows[x]);
  621. }
  622. }
  623. var xAxisIndex, column;
  624. // Add computed column headers and top level headers to final row set
  625. dataRows = multiLevelHeaders ? [topLevelColumnTitles, columnTitles] :
  626. [columnTitles];
  627. i = xAxisIndices.length;
  628. while (i--) { // Start from end to splice in
  629. xAxisIndex = xAxisIndices[i][0];
  630. column = xAxisIndices[i][1];
  631. xAxis = xAxes[xAxisIndex];
  632. // Sort it by X values
  633. rowArr.sort(function (a, b) { // eslint-disable-line no-loop-func
  634. return a.xValues[xAxisIndex] - b.xValues[xAxisIndex];
  635. });
  636. // Add header row
  637. xTitle = columnHeaderFormatter(xAxis);
  638. dataRows[0].splice(column, 0, xTitle);
  639. if (multiLevelHeaders && dataRows[1]) {
  640. // If using multi level headers, we just added top level header.
  641. // Also add for sub level
  642. dataRows[1].splice(column, 0, xTitle);
  643. }
  644. // Add the category column
  645. rowArr.forEach(function (row) { // eslint-disable-line no-loop-func
  646. var category = row.name;
  647. if (xAxis && !defined(category)) {
  648. if (xAxis.isDatetimeAxis) {
  649. if (row.x instanceof Date) {
  650. row.x = row.x.getTime();
  651. }
  652. category = time.dateFormat(
  653. csvOptions.dateFormat,
  654. row.x
  655. );
  656. } else if (xAxis.categories) {
  657. category = pick(
  658. xAxis.names[row.x],
  659. xAxis.categories[row.x],
  660. row.x
  661. );
  662. } else {
  663. category = row.x;
  664. }
  665. }
  666. // Add the X/date/category
  667. row.splice(column, 0, category);
  668. });
  669. }
  670. dataRows = dataRows.concat(rowArr);
  671. Highcharts.fireEvent(this, 'exportData', { dataRows: dataRows });
  672. return dataRows;
  673. };
  674. /**
  675. * Export-data module required. Returns the current chart data as a CSV string.
  676. *
  677. * @function Highcharts.Chart#getCSV
  678. *
  679. * @param {boolean} useLocalDecimalPoint
  680. * Whether to use the local decimal point as detected from the browser.
  681. * This makes it easier to export data to Excel in the same locale as the
  682. * user is.
  683. *
  684. * @return {string}
  685. * CSV representation of the data
  686. */
  687. Highcharts.Chart.prototype.getCSV = function (useLocalDecimalPoint) {
  688. var csv = '',
  689. rows = this.getDataRows(),
  690. csvOptions = this.options.exporting.csv,
  691. decimalPoint = pick(
  692. csvOptions.decimalPoint,
  693. csvOptions.itemDelimiter !== ',' && useLocalDecimalPoint ?
  694. (1.1).toLocaleString()[1] :
  695. '.'
  696. ),
  697. // use ';' for direct to Excel
  698. itemDelimiter = pick(
  699. csvOptions.itemDelimiter,
  700. decimalPoint === ',' ? ';' : ','
  701. ),
  702. // '\n' isn't working with the js csv data extraction
  703. lineDelimiter = csvOptions.lineDelimiter;
  704. // Transform the rows to CSV
  705. rows.forEach(function (row, i) {
  706. var val = '',
  707. j = row.length;
  708. while (j--) {
  709. val = row[j];
  710. if (typeof val === 'string') {
  711. val = '"' + val + '"';
  712. }
  713. if (typeof val === 'number') {
  714. if (decimalPoint !== '.') {
  715. val = val.toString().replace('.', decimalPoint);
  716. }
  717. }
  718. row[j] = val;
  719. }
  720. // Add the values
  721. csv += row.join(itemDelimiter);
  722. // Add the line delimiter
  723. if (i < rows.length - 1) {
  724. csv += lineDelimiter;
  725. }
  726. });
  727. return csv;
  728. };
  729. /**
  730. * Export-data module required. Build a HTML table with the chart's current
  731. * data.
  732. *
  733. * @sample highcharts/export-data/viewdata/
  734. * View the data from the export menu
  735. *
  736. * @function Highcharts.Chart#getTable
  737. *
  738. * @param {boolean} useLocalDecimalPoint
  739. * Whether to use the local decimal point as detected from the browser.
  740. * This makes it easier to export data to Excel in the same locale as the
  741. * user is.
  742. *
  743. * @return {string}
  744. * HTML representation of the data.
  745. */
  746. Highcharts.Chart.prototype.getTable = function (useLocalDecimalPoint) {
  747. var html = '<table id="highcharts-data-table-' + this.index + '">',
  748. options = this.options,
  749. decimalPoint = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.',
  750. useMultiLevelHeaders = pick(
  751. options.exporting.useMultiLevelHeaders, true
  752. ),
  753. rows = this.getDataRows(useMultiLevelHeaders),
  754. rowLength = 0,
  755. topHeaders = useMultiLevelHeaders ? rows.shift() : null,
  756. subHeaders = rows.shift(),
  757. // Compare two rows for equality
  758. isRowEqual = function (row1, row2) {
  759. var i = row1.length;
  760. if (row2.length === i) {
  761. while (i--) {
  762. if (row1[i] !== row2[i]) {
  763. return false;
  764. }
  765. }
  766. } else {
  767. return false;
  768. }
  769. return true;
  770. },
  771. // Get table cell HTML from value
  772. getCellHTMLFromValue = function (tag, classes, attrs, value) {
  773. var val = pick(value, ''),
  774. className = 'text' + (classes ? ' ' + classes : '');
  775. // Convert to string if number
  776. if (typeof val === 'number') {
  777. val = val.toString();
  778. if (decimalPoint === ',') {
  779. val = val.replace('.', decimalPoint);
  780. }
  781. className = 'number';
  782. } else if (!value) {
  783. className = 'empty';
  784. }
  785. return '<' + tag + (attrs ? ' ' + attrs : '') +
  786. ' class="' + className + '">' +
  787. val + '</' + tag + '>';
  788. },
  789. // Get table header markup from row data
  790. getTableHeaderHTML = function (topheaders, subheaders, rowLength) {
  791. var html = '<thead>',
  792. i = 0,
  793. len = rowLength || subheaders && subheaders.length,
  794. next,
  795. cur,
  796. curColspan = 0,
  797. rowspan;
  798. // Clean up multiple table headers. Chart.getDataRows() returns two
  799. // levels of headers when using multilevel, not merged. We need to
  800. // merge identical headers, remove redundant headers, and keep it
  801. // all marked up nicely.
  802. if (
  803. useMultiLevelHeaders &&
  804. topheaders &&
  805. subheaders &&
  806. !isRowEqual(topheaders, subheaders)
  807. ) {
  808. html += '<tr>';
  809. for (; i < len; ++i) {
  810. cur = topheaders[i];
  811. next = topheaders[i + 1];
  812. if (cur === next) {
  813. ++curColspan;
  814. } else if (curColspan) {
  815. // Ended colspan
  816. // Add cur to HTML with colspan.
  817. html += getCellHTMLFromValue(
  818. 'th',
  819. 'highcharts-table-topheading',
  820. 'scope="col" ' +
  821. 'colspan="' + (curColspan + 1) + '"',
  822. cur
  823. );
  824. curColspan = 0;
  825. } else {
  826. // Cur is standalone. If it is same as sublevel,
  827. // remove sublevel and add just toplevel.
  828. if (cur === subheaders[i]) {
  829. if (options.exporting.useRowspanHeaders) {
  830. rowspan = 2;
  831. delete subheaders[i];
  832. } else {
  833. rowspan = 1;
  834. subheaders[i] = '';
  835. }
  836. } else {
  837. rowspan = 1;
  838. }
  839. html += getCellHTMLFromValue(
  840. 'th',
  841. 'highcharts-table-topheading',
  842. 'scope="col"' +
  843. (rowspan > 1 ?
  844. ' valign="top" rowspan="' + rowspan + '"' :
  845. ''),
  846. cur
  847. );
  848. }
  849. }
  850. html += '</tr>';
  851. }
  852. // Add the subheaders (the only headers if not using multilevels)
  853. if (subheaders) {
  854. html += '<tr>';
  855. for (i = 0, len = subheaders.length; i < len; ++i) {
  856. if (subheaders[i] !== undefined) {
  857. html += getCellHTMLFromValue(
  858. 'th', null, 'scope="col"', subheaders[i]
  859. );
  860. }
  861. }
  862. html += '</tr>';
  863. }
  864. html += '</thead>';
  865. return html;
  866. };
  867. // Add table caption
  868. if (options.exporting.tableCaption !== false) {
  869. html += '<caption class="highcharts-table-caption">' + pick(
  870. options.exporting.tableCaption,
  871. (
  872. options.title.text ?
  873. htmlencode(options.title.text) :
  874. 'Chart'
  875. )
  876. ) + '</caption>';
  877. }
  878. // Find longest row
  879. for (var i = 0, len = rows.length; i < len; ++i) {
  880. if (rows[i].length > rowLength) {
  881. rowLength = rows[i].length;
  882. }
  883. }
  884. // Add header
  885. html += getTableHeaderHTML(
  886. topHeaders,
  887. subHeaders,
  888. Math.max(rowLength, subHeaders.length)
  889. );
  890. // Transform the rows to HTML
  891. html += '<tbody>';
  892. rows.forEach(function (row) {
  893. html += '<tr>';
  894. for (var j = 0; j < rowLength; j++) {
  895. // Make first column a header too. Especially important for
  896. // category axes, but also might make sense for datetime? Should
  897. // await user feedback on this.
  898. html += getCellHTMLFromValue(
  899. j ? 'td' : 'th',
  900. null,
  901. j ? '' : 'scope="row"',
  902. row[j]
  903. );
  904. }
  905. html += '</tr>';
  906. });
  907. html += '</tbody></table>';
  908. var e = { html: html };
  909. Highcharts.fireEvent(this, 'afterGetTable', e);
  910. return e.html;
  911. };
  912. /**
  913. * Get a blob object from content, if blob is supported
  914. *
  915. * @private
  916. *
  917. * @param {String} content The content to create the blob from.
  918. * @param {String} type The type of the content.
  919. * @return {object} The blob object, or undefined if not supported.
  920. */
  921. function getBlobFromContent(content, type) {
  922. if (win.Blob && win.navigator.msSaveOrOpenBlob) {
  923. return new win.Blob(
  924. ['\uFEFF' + content], // #7084
  925. { type: type }
  926. );
  927. }
  928. }
  929. /**
  930. * Call this on click of 'Download CSV' button
  931. *
  932. * @private
  933. * @function Highcharts.Chart#downloadCSV
  934. */
  935. Highcharts.Chart.prototype.downloadCSV = function () {
  936. var csv = this.getCSV(true);
  937. downloadURL(
  938. getBlobFromContent(csv, 'text/csv') ||
  939. 'data:text/csv,\uFEFF' + encodeURIComponent(csv),
  940. this.getFilename() + '.csv'
  941. );
  942. };
  943. /**
  944. * Call this on click of 'Download XLS' button
  945. *
  946. * @private
  947. * @function Highcharts.Chart#downloadXLS
  948. */
  949. Highcharts.Chart.prototype.downloadXLS = function () {
  950. var uri = 'data:application/vnd.ms-excel;base64,',
  951. template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" ' +
  952. 'xmlns:x="urn:schemas-microsoft-com:office:excel" ' +
  953. 'xmlns="http://www.w3.org/TR/REC-html40">' +
  954. '<head><!--[if gte mso 9]><xml><x:ExcelWorkbook>' +
  955. '<x:ExcelWorksheets><x:ExcelWorksheet>' +
  956. '<x:Name>Ark1</x:Name>' +
  957. '<x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>' +
  958. '</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook>' +
  959. '</xml><![endif]-->' +
  960. '<style>td{border:none;font-family: Calibri, sans-serif;} ' +
  961. '.number{mso-number-format:"0.00";} ' +
  962. '.text{ mso-number-format:"\@";}</style>' +
  963. '<meta name=ProgId content=Excel.Sheet>' +
  964. '<meta charset=UTF-8>' +
  965. '</head><body>' +
  966. this.getTable(true) +
  967. '</body></html>',
  968. base64 = function (s) {
  969. return win.btoa(unescape(encodeURIComponent(s))); // #50
  970. };
  971. downloadURL(
  972. getBlobFromContent(template, 'application/vnd.ms-excel') ||
  973. uri + base64(template),
  974. this.getFilename() + '.xls'
  975. );
  976. };
  977. /**
  978. * Export-data module required. View the data in a table below the chart.
  979. *
  980. * @function Highcharts.Chart#viewData
  981. */
  982. Highcharts.Chart.prototype.viewData = function () {
  983. if (!this.dataTableDiv) {
  984. this.dataTableDiv = doc.createElement('div');
  985. this.dataTableDiv.className = 'highcharts-data-table';
  986. // Insert after the chart container
  987. this.renderTo.parentNode.insertBefore(
  988. this.dataTableDiv,
  989. this.renderTo.nextSibling
  990. );
  991. }
  992. this.dataTableDiv.innerHTML = this.getTable();
  993. };
  994. /**
  995. * Experimental function to send a chart's config to the Cloud for editing.
  996. *
  997. * Limitations
  998. * - All functions (formatters and callbacks) are removed since they're not
  999. * JSON.
  1000. *
  1001. * @function Highcharts.Chart#openInCloud
  1002. *
  1003. * @todo
  1004. * - Let the Cloud throw a friendly warning about unsupported structures like
  1005. * formatters.
  1006. * - Dynamically updated charts probably fail, we need a generic
  1007. * Chart.getOptions function that returns all non-default options. Should also
  1008. * be used by the export module.
  1009. */
  1010. Highcharts.Chart.prototype.openInCloud = function () {
  1011. var options,
  1012. paramObj,
  1013. params;
  1014. // Recursively remove function callbacks
  1015. function removeFunctions(ob) {
  1016. Object.keys(ob).forEach(function (key) {
  1017. if (typeof ob[key] === 'function') {
  1018. delete ob[key];
  1019. }
  1020. if (Highcharts.isObject(ob[key])) { // object and not an array
  1021. removeFunctions(ob[key]);
  1022. }
  1023. });
  1024. }
  1025. function openInCloud() {
  1026. var form = doc.createElement('form');
  1027. doc.body.appendChild(form);
  1028. form.method = 'post';
  1029. form.action = 'https://cloud-api.highcharts.com/openincloud';
  1030. form.target = '_blank';
  1031. var input = doc.createElement('input');
  1032. input.type = 'hidden';
  1033. input.name = 'chart';
  1034. input.value = params;
  1035. form.appendChild(input);
  1036. form.submit();
  1037. doc.body.removeChild(form);
  1038. }
  1039. options = Highcharts.merge(this.userOptions);
  1040. removeFunctions(options);
  1041. paramObj = {
  1042. name: (options.title && options.title.text) || 'Chart title',
  1043. options: options,
  1044. settings: {
  1045. constructor: 'Chart',
  1046. dataProvider: {
  1047. csv: this.getCSV()
  1048. }
  1049. }
  1050. };
  1051. params = JSON.stringify(paramObj);
  1052. openInCloud();
  1053. };
  1054. // Add "Download CSV" to the exporting menu.
  1055. var exportingOptions = Highcharts.getOptions().exporting;
  1056. if (exportingOptions) {
  1057. Highcharts.extend(exportingOptions.menuItemDefinitions, {
  1058. downloadCSV: {
  1059. textKey: 'downloadCSV',
  1060. onclick: function () {
  1061. this.downloadCSV();
  1062. }
  1063. },
  1064. downloadXLS: {
  1065. textKey: 'downloadXLS',
  1066. onclick: function () {
  1067. this.downloadXLS();
  1068. }
  1069. },
  1070. viewData: {
  1071. textKey: 'viewData',
  1072. onclick: function () {
  1073. this.viewData();
  1074. }
  1075. },
  1076. openInCloud: {
  1077. textKey: 'openInCloud',
  1078. onclick: function () {
  1079. this.openInCloud();
  1080. }
  1081. }
  1082. });
  1083. exportingOptions.buttons.contextButton.menuItems.push(
  1084. 'separator',
  1085. 'downloadCSV',
  1086. 'downloadXLS',
  1087. 'viewData',
  1088. 'openInCloud'
  1089. );
  1090. }
  1091. // Series specific
  1092. if (seriesTypes.map) {
  1093. seriesTypes.map.prototype.exportKey = 'name';
  1094. }
  1095. if (seriesTypes.mapbubble) {
  1096. seriesTypes.mapbubble.prototype.exportKey = 'name';
  1097. }
  1098. if (seriesTypes.treemap) {
  1099. seriesTypes.treemap.prototype.exportKey = 'name';
  1100. }
  1101. }(Highcharts));
  1102. return (function () {
  1103. }());
  1104. }));