export-data.src.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Exporting module
  4. *
  5. * (c) 2010-2017 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. (function (H) {
  18. /**
  19. * (c) 2010-2017 Christer Vasseng, Torstein Honsi
  20. *
  21. * License: www.highcharts.com/license
  22. */
  23. /**
  24. * @typedef {Object} AjaxSettings
  25. * @property {String} url - The URL to call
  26. * @property {('get'|'post'|'update'|'delete')} type - The verb to use
  27. * @property {('json'|'xml'|'text'|'octet')} dataType - The data type expected
  28. * @property {Function} success - Function to call on success
  29. * @property {Function} error - Function to call on error
  30. * @property {Object} data - The payload to send
  31. * @property {Object} headers - The headers; keyed on header name
  32. */
  33. /**
  34. * Perform an Ajax call.
  35. *
  36. * @memberof Highcharts
  37. * @param {AjaxSettings} - The Ajax settings to use
  38. *
  39. */
  40. H.ajax = function (attr) {
  41. var options = H.merge(true, {
  42. url: false,
  43. type: 'GET',
  44. dataType: 'json',
  45. success: false,
  46. error: false,
  47. data: false,
  48. headers: {}
  49. }, attr),
  50. headers = {
  51. json: 'application/json',
  52. xml: 'application/xml',
  53. text: 'text/plain',
  54. octet: 'application/octet-stream'
  55. },
  56. r = new XMLHttpRequest();
  57. function handleError(xhr, err) {
  58. if (options.error) {
  59. options.error(xhr, err);
  60. } else {
  61. // Maybe emit a highcharts error event here
  62. }
  63. }
  64. if (!options.url) {
  65. return false;
  66. }
  67. r.open(options.type.toUpperCase(), options.url, true);
  68. r.setRequestHeader(
  69. 'Content-Type',
  70. headers[options.dataType] || headers.text
  71. );
  72. H.objectEach(options.headers, function (val, key) {
  73. r.setRequestHeader(key, val);
  74. });
  75. r.onreadystatechange = function () {
  76. var res;
  77. if (r.readyState === 4) {
  78. if (r.status === 200) {
  79. res = r.responseText;
  80. if (options.dataType === 'json') {
  81. try {
  82. res = JSON.parse(res);
  83. } catch (e) {
  84. return handleError(r, e);
  85. }
  86. }
  87. return options.success && options.success(res);
  88. }
  89. handleError(r, r.responseText);
  90. }
  91. };
  92. try {
  93. options.data = JSON.stringify(options.data);
  94. } catch (e) {}
  95. r.send(options.data || true);
  96. };
  97. }(Highcharts));
  98. (function (Highcharts) {
  99. /**
  100. * Experimental data export module for Highcharts
  101. *
  102. * (c) 2010-2017 Torstein Honsi
  103. *
  104. * License: www.highcharts.com/license
  105. */
  106. // @todo
  107. // - Set up systematic tests for all series types, paired with tests of the data
  108. // module importing the same data.
  109. var defined = Highcharts.defined,
  110. each = Highcharts.each,
  111. pick = Highcharts.pick,
  112. win = Highcharts.win,
  113. doc = win.document,
  114. seriesTypes = Highcharts.seriesTypes,
  115. downloadAttrSupported = doc.createElement('a').download !== undefined;
  116. // Can we add this to utils? Also used in screen-reader.js
  117. /**
  118. * HTML encode some characters vulnerable for XSS.
  119. * @param {string} html The input string
  120. * @return {string} The excaped string
  121. */
  122. function htmlencode(html) {
  123. return html
  124. .replace(/&/g, '&')
  125. .replace(/</g, '&lt;')
  126. .replace(/>/g, '&gt;')
  127. .replace(/"/g, '&quot;')
  128. .replace(/'/g, '&#x27;')
  129. .replace(/\//g, '&#x2F;');
  130. }
  131. Highcharts.setOptions({
  132. /**
  133. * @optionparent exporting
  134. */
  135. exporting: {
  136. /**
  137. * Export-data module required. Caption for the data table. Same as
  138. * chart title by default. Set to `false` to disable.
  139. *
  140. * @type {Boolean|String}
  141. * @since 6.0.4
  142. * @sample highcharts/export-data/multilevel-table
  143. * Multiple table headers
  144. * @default undefined
  145. * @apioption exporting.tableCaption
  146. */
  147. /**
  148. * Options for exporting data to CSV or ExCel, or displaying the data
  149. * in a HTML table or a JavaScript structure. Requires the
  150. * `export-data.js` module. This module adds data export options to the
  151. * export menu and provides functions like `Chart.getCSV`,
  152. * `Chart.getTable`, `Chart.getDataRows` and `Chart.viewData`.
  153. *
  154. * @sample highcharts/export-data/categorized/ Categorized data
  155. * @sample highcharts/export-data/stock-timeaxis/ Highstock time axis
  156. *
  157. * @since 6.0.0
  158. */
  159. csv: {
  160. /**
  161. * Formatter callback for the column headers. Parameters are:
  162. * - `item` - The series or axis object)
  163. * - `key` - The point key, for example y or z
  164. * - `keyLength` - The amount of value keys for this item, for
  165. * example a range series has the keys `low` and `high` so the
  166. * key length is 2.
  167. *
  168. * If [useMultiLevelHeaders](#exporting.useMultiLevelHeaders) is
  169. * true, columnHeaderFormatter by default returns an object with
  170. * columnTitle and topLevelColumnTitle for each key. Columns with
  171. * the same topLevelColumnTitle have their titles merged into a
  172. * single cell with colspan for table/Excel export.
  173. *
  174. * If `useMultiLevelHeaders` is false, or for CSV export, it returns
  175. * the series name, followed by the key if there is more than one
  176. * key.
  177. *
  178. * For the axis it returns the axis title or "Category" or
  179. * "DateTime" by default.
  180. *
  181. * Return `false` to use Highcharts' proposed header.
  182. *
  183. * @sample highcharts/export-data/multilevel-table
  184. * Multiple table headers
  185. * @type {Function|null}
  186. */
  187. columnHeaderFormatter: null,
  188. /**
  189. * Which date format to use for exported dates on a datetime X axis.
  190. * See `Highcharts.dateFormat`.
  191. */
  192. dateFormat: '%Y-%m-%d %H:%M:%S',
  193. /**
  194. * Which decimal point to use for exported CSV. Defaults to the same
  195. * as the browser locale, typically `.` (English) or `,` (German,
  196. * French etc).
  197. * @type {String}
  198. * @since 6.0.4
  199. */
  200. decimalPoint: null,
  201. /**
  202. * The item delimiter in the exported data. Use `;` for direct
  203. * exporting to Excel. Defaults to a best guess based on the browser
  204. * locale. If the locale _decimal point_ is `,`, the `itemDelimiter`
  205. * defaults to `;`, otherwise the `itemDelimiter` defaults to `,`.
  206. *
  207. * @type {String}
  208. */
  209. itemDelimiter: null,
  210. /**
  211. * The line delimiter in the exported data, defaults to a newline.
  212. */
  213. lineDelimiter: '\n'
  214. },
  215. /**
  216. * Export-data module required. Show a HTML table below the chart with
  217. * the chart's current data.
  218. *
  219. * @sample highcharts/export-data/showtable/ Show the table
  220. * @since 6.0.0
  221. */
  222. showTable: false,
  223. /**
  224. * Export-data module required. Use multi level headers in data table.
  225. * If [csv.columnHeaderFormatter](#exporting.csv.columnHeaderFormatter)
  226. * is defined, it has to return objects in order for multi level headers
  227. * to work.
  228. *
  229. * @sample highcharts/export-data/multilevel-table
  230. * Multiple table headers
  231. * @since 6.0.4
  232. */
  233. useMultiLevelHeaders: true,
  234. /**
  235. * Export-data module required. If using multi level table headers, use
  236. * rowspans for headers that have only one level.
  237. *
  238. * @sample highcharts/export-data/multilevel-table
  239. * Multiple table headers
  240. * @since 6.0.4
  241. */
  242. useRowspanHeaders: true
  243. },
  244. /**
  245. * @optionparent lang
  246. */
  247. lang: {
  248. /**
  249. * Export-data module only. The text for the menu item.
  250. * @since 6.0.0
  251. */
  252. downloadCSV: 'Download CSV',
  253. /**
  254. * Export-data module only. The text for the menu item.
  255. * @since 6.0.0
  256. */
  257. downloadXLS: 'Download XLS',
  258. /**
  259. * Export-data module only. The text for the menu item.
  260. * @since 6.1.0
  261. */
  262. openInCloud: 'Open in Highcharts Cloud',
  263. /**
  264. * Export-data module only. The text for the menu item.
  265. * @since 6.0.0
  266. */
  267. viewData: 'View data table'
  268. }
  269. });
  270. // Add an event listener to handle the showTable option
  271. Highcharts.addEvent(Highcharts.Chart, 'render', function () {
  272. if (
  273. this.options &&
  274. this.options.exporting &&
  275. this.options.exporting.showTable
  276. ) {
  277. this.viewData();
  278. }
  279. });
  280. // Set up key-to-axis bindings. This is used when the Y axis is datetime or
  281. // categorized. For example in an arearange series, the low and high values
  282. // sholud be formatted according to the Y axis type, and in order to link them
  283. // we need this map.
  284. Highcharts.Chart.prototype.setUpKeyToAxis = function () {
  285. if (seriesTypes.arearange) {
  286. seriesTypes.arearange.prototype.keyToAxis = {
  287. low: 'y',
  288. high: 'y'
  289. };
  290. }
  291. };
  292. /**
  293. * Export-data module required. Returns a two-dimensional array containing the
  294. * current chart data.
  295. *
  296. * @param {Boolean} multiLevelHeaders
  297. * Use multilevel headers for the rows by default. Adds an extra row
  298. * with top level headers. If a custom columnHeaderFormatter is
  299. * defined, this can override the behavior.
  300. *
  301. * @returns {Array.<Array>}
  302. * The current chart data
  303. */
  304. Highcharts.Chart.prototype.getDataRows = function (multiLevelHeaders) {
  305. var time = this.time,
  306. csvOptions = (this.options.exporting && this.options.exporting.csv) ||
  307. {},
  308. xAxis,
  309. xAxes = this.xAxis,
  310. rows = {},
  311. rowArr = [],
  312. dataRows,
  313. topLevelColumnTitles = [],
  314. columnTitles = [],
  315. columnTitleObj,
  316. i,
  317. x,
  318. xTitle,
  319. // Options
  320. columnHeaderFormatter = function (item, key, keyLength) {
  321. if (csvOptions.columnHeaderFormatter) {
  322. var s = csvOptions.columnHeaderFormatter(item, key, keyLength);
  323. if (s !== false) {
  324. return s;
  325. }
  326. }
  327. if (!item) {
  328. return 'Category';
  329. }
  330. if (item instanceof Highcharts.Axis) {
  331. return (item.options.title && item.options.title.text) ||
  332. (item.isDatetimeAxis ? 'DateTime' : 'Category');
  333. }
  334. if (multiLevelHeaders) {
  335. return {
  336. columnTitle: keyLength > 1 ? key : item.name,
  337. topLevelColumnTitle: item.name
  338. };
  339. }
  340. return item.name + (keyLength > 1 ? ' (' + key + ')' : '');
  341. },
  342. xAxisIndices = [];
  343. // Loop the series and index values
  344. i = 0;
  345. this.setUpKeyToAxis();
  346. each(this.series, function (series) {
  347. var keys = series.options.keys,
  348. pointArrayMap = keys || series.pointArrayMap || ['y'],
  349. valueCount = pointArrayMap.length,
  350. xTaken = !series.requireSorting && {},
  351. categoryMap = {},
  352. datetimeValueAxisMap = {},
  353. xAxisIndex = Highcharts.inArray(series.xAxis, xAxes),
  354. mockSeries,
  355. j;
  356. // Map the categories for value axes
  357. each(pointArrayMap, function (prop) {
  358. var axisName = (
  359. (series.keyToAxis && series.keyToAxis[prop]) ||
  360. prop
  361. ) + 'Axis';
  362. categoryMap[prop] = (
  363. series[axisName] &&
  364. series[axisName].categories
  365. ) || [];
  366. datetimeValueAxisMap[prop] = (
  367. series[axisName] &&
  368. series[axisName].isDatetimeAxis
  369. );
  370. });
  371. if (
  372. series.options.includeInCSVExport !== false &&
  373. !series.options.isInternal &&
  374. series.visible !== false // #55
  375. ) {
  376. // Build a lookup for X axis index and the position of the first
  377. // series that belongs to that X axis. Includes -1 for non-axis
  378. // series types like pies.
  379. if (!Highcharts.find(xAxisIndices, function (index) {
  380. return index[0] === xAxisIndex;
  381. })) {
  382. xAxisIndices.push([xAxisIndex, i]);
  383. }
  384. // Compute the column headers and top level headers, usually the
  385. // same as series names
  386. j = 0;
  387. while (j < valueCount) {
  388. columnTitleObj = columnHeaderFormatter(
  389. series,
  390. pointArrayMap[j],
  391. pointArrayMap.length
  392. );
  393. columnTitles.push(
  394. columnTitleObj.columnTitle || columnTitleObj
  395. );
  396. if (multiLevelHeaders) {
  397. topLevelColumnTitles.push(
  398. columnTitleObj.topLevelColumnTitle || columnTitleObj
  399. );
  400. }
  401. j++;
  402. }
  403. mockSeries = {
  404. chart: series.chart,
  405. autoIncrement: series.autoIncrement,
  406. options: series.options,
  407. pointArrayMap: series.pointArrayMap
  408. };
  409. // Export directly from options.data because we need the uncropped
  410. // data (#7913), and we need to support Boost (#7026).
  411. each(series.options.data, function eachData(options, pIdx) {
  412. var key,
  413. prop,
  414. val,
  415. point;
  416. point = { series: mockSeries };
  417. series.pointClass.prototype.applyOptions.apply(
  418. point,
  419. [options]
  420. );
  421. key = point.x;
  422. if (xTaken) {
  423. if (xTaken[key]) {
  424. key += '|' + pIdx;
  425. }
  426. xTaken[key] = true;
  427. }
  428. j = 0;
  429. if (!rows[key]) {
  430. // Generate the row
  431. rows[key] = [];
  432. // Contain the X values from one or more X axes
  433. rows[key].xValues = [];
  434. }
  435. rows[key].x = point.x;
  436. rows[key].xValues[xAxisIndex] = point.x;
  437. // Pies, funnels, geo maps etc. use point name in X row
  438. if (!series.xAxis || series.exportKey === 'name') {
  439. rows[key].name = (
  440. series.data[pIdx] &&
  441. series.data[pIdx].name
  442. );
  443. }
  444. while (j < valueCount) {
  445. prop = pointArrayMap[j]; // y, z etc
  446. val = point[prop];
  447. rows[key][i + j] = pick(
  448. categoryMap[prop][val], // Y axis category if present
  449. datetimeValueAxisMap[prop] ?
  450. time.dateFormat(csvOptions.dateFormat, val) :
  451. null,
  452. val
  453. );
  454. j++;
  455. }
  456. });
  457. i = i + j;
  458. }
  459. });
  460. // Make a sortable array
  461. for (x in rows) {
  462. if (rows.hasOwnProperty(x)) {
  463. rowArr.push(rows[x]);
  464. }
  465. }
  466. var xAxisIndex, column;
  467. // Add computed column headers and top level headers to final row set
  468. dataRows = multiLevelHeaders ? [topLevelColumnTitles, columnTitles] :
  469. [columnTitles];
  470. i = xAxisIndices.length;
  471. while (i--) { // Start from end to splice in
  472. xAxisIndex = xAxisIndices[i][0];
  473. column = xAxisIndices[i][1];
  474. xAxis = xAxes[xAxisIndex];
  475. // Sort it by X values
  476. rowArr.sort(function (a, b) { // eslint-disable-line no-loop-func
  477. return a.xValues[xAxisIndex] - b.xValues[xAxisIndex];
  478. });
  479. // Add header row
  480. xTitle = columnHeaderFormatter(xAxis);
  481. dataRows[0].splice(column, 0, xTitle);
  482. if (multiLevelHeaders && dataRows[1]) {
  483. // If using multi level headers, we just added top level header.
  484. // Also add for sub level
  485. dataRows[1].splice(column, 0, xTitle);
  486. }
  487. // Add the category column
  488. each(rowArr, function (row) { // eslint-disable-line no-loop-func
  489. var category = row.name;
  490. if (xAxis && !defined(category)) {
  491. if (xAxis.isDatetimeAxis) {
  492. if (row.x instanceof Date) {
  493. row.x = row.x.getTime();
  494. }
  495. category = time.dateFormat(
  496. csvOptions.dateFormat,
  497. row.x
  498. );
  499. } else if (xAxis.categories) {
  500. category = pick(
  501. xAxis.names[row.x],
  502. xAxis.categories[row.x],
  503. row.x
  504. );
  505. } else {
  506. category = row.x;
  507. }
  508. }
  509. // Add the X/date/category
  510. row.splice(column, 0, category);
  511. });
  512. }
  513. dataRows = dataRows.concat(rowArr);
  514. return dataRows;
  515. };
  516. /**
  517. * Export-data module required. Returns the current chart data as a CSV string.
  518. *
  519. * @param {Boolean} useLocalDecimalPoint
  520. * Whether to use the local decimal point as detected from the browser.
  521. * This makes it easier to export data to Excel in the same locale as
  522. * the user is.
  523. *
  524. * @returns {String}
  525. * CSV representation of the data
  526. */
  527. Highcharts.Chart.prototype.getCSV = function (useLocalDecimalPoint) {
  528. var csv = '',
  529. rows = this.getDataRows(),
  530. csvOptions = this.options.exporting.csv,
  531. decimalPoint = pick(
  532. csvOptions.decimalPoint,
  533. csvOptions.itemDelimiter !== ',' && useLocalDecimalPoint ?
  534. (1.1).toLocaleString()[1] :
  535. '.'
  536. ),
  537. // use ';' for direct to Excel
  538. itemDelimiter = pick(
  539. csvOptions.itemDelimiter,
  540. decimalPoint === ',' ? ';' : ','
  541. ),
  542. // '\n' isn't working with the js csv data extraction
  543. lineDelimiter = csvOptions.lineDelimiter;
  544. // Transform the rows to CSV
  545. each(rows, function (row, i) {
  546. var val = '',
  547. j = row.length;
  548. while (j--) {
  549. val = row[j];
  550. if (typeof val === 'string') {
  551. val = '"' + val + '"';
  552. }
  553. if (typeof val === 'number') {
  554. if (decimalPoint !== '.') {
  555. val = val.toString().replace('.', decimalPoint);
  556. }
  557. }
  558. row[j] = val;
  559. }
  560. // Add the values
  561. csv += row.join(itemDelimiter);
  562. // Add the line delimiter
  563. if (i < rows.length - 1) {
  564. csv += lineDelimiter;
  565. }
  566. });
  567. return csv;
  568. };
  569. /**
  570. * Export-data module required. Build a HTML table with the chart's current
  571. * data.
  572. *
  573. * @sample highcharts/export-data/viewdata/
  574. * View the data from the export menu
  575. * @returns {String}
  576. * HTML representation of the data.
  577. */
  578. Highcharts.Chart.prototype.getTable = function (useLocalDecimalPoint) {
  579. var html = '<table>',
  580. options = this.options,
  581. decimalPoint = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.',
  582. useMultiLevelHeaders = pick(
  583. options.exporting.useMultiLevelHeaders, true
  584. ),
  585. rows = this.getDataRows(useMultiLevelHeaders),
  586. rowLength = 0,
  587. topHeaders = useMultiLevelHeaders ? rows.shift() : null,
  588. subHeaders = rows.shift(),
  589. // Compare two rows for equality
  590. isRowEqual = function (row1, row2) {
  591. var i = row1.length;
  592. if (row2.length === i) {
  593. while (i--) {
  594. if (row1[i] !== row2[i]) {
  595. return false;
  596. }
  597. }
  598. } else {
  599. return false;
  600. }
  601. return true;
  602. },
  603. // Get table cell HTML from value
  604. getCellHTMLFromValue = function (tag, classes, attrs, value) {
  605. var val = pick(value, ''),
  606. className = 'text' + (classes ? ' ' + classes : '');
  607. // Convert to string if number
  608. if (typeof val === 'number') {
  609. val = val.toString();
  610. if (decimalPoint === ',') {
  611. val = val.replace('.', decimalPoint);
  612. }
  613. className = 'number';
  614. } else if (!value) {
  615. className = 'empty';
  616. }
  617. return '<' + tag + (attrs ? ' ' + attrs : '') +
  618. ' class="' + className + '">' +
  619. val + '</' + tag + '>';
  620. },
  621. // Get table header markup from row data
  622. getTableHeaderHTML = function (topheaders, subheaders, rowLength) {
  623. var html = '<thead>',
  624. i = 0,
  625. len = rowLength || subheaders && subheaders.length,
  626. next,
  627. cur,
  628. curColspan = 0,
  629. rowspan;
  630. // Clean up multiple table headers. Chart.getDataRows() returns two
  631. // levels of headers when using multilevel, not merged. We need to
  632. // merge identical headers, remove redundant headers, and keep it
  633. // all marked up nicely.
  634. if (
  635. useMultiLevelHeaders &&
  636. topheaders &&
  637. subheaders &&
  638. !isRowEqual(topheaders, subheaders)
  639. ) {
  640. html += '<tr>';
  641. for (; i < len; ++i) {
  642. cur = topheaders[i];
  643. next = topheaders[i + 1];
  644. if (cur === next) {
  645. ++curColspan;
  646. } else if (curColspan) {
  647. // Ended colspan
  648. // Add cur to HTML with colspan.
  649. html += getCellHTMLFromValue(
  650. 'th',
  651. 'highcharts-table-topheading',
  652. 'scope="col" ' +
  653. 'colspan="' + (curColspan + 1) + '"',
  654. cur
  655. );
  656. curColspan = 0;
  657. } else {
  658. // Cur is standalone. If it is same as sublevel,
  659. // remove sublevel and add just toplevel.
  660. if (cur === subheaders[i]) {
  661. if (options.exporting.useRowspanHeaders) {
  662. rowspan = 2;
  663. delete subheaders[i];
  664. } else {
  665. rowspan = 1;
  666. subheaders[i] = '';
  667. }
  668. } else {
  669. rowspan = 1;
  670. }
  671. html += getCellHTMLFromValue(
  672. 'th',
  673. 'highcharts-table-topheading',
  674. 'scope="col"' +
  675. (rowspan > 1 ?
  676. ' valign="top" rowspan="' + rowspan + '"' :
  677. ''),
  678. cur
  679. );
  680. }
  681. }
  682. html += '</tr>';
  683. }
  684. // Add the subheaders (the only headers if not using multilevels)
  685. if (subheaders) {
  686. html += '<tr>';
  687. for (i = 0, len = subheaders.length; i < len; ++i) {
  688. if (subheaders[i] !== undefined) {
  689. html += getCellHTMLFromValue(
  690. 'th', null, 'scope="col"', subheaders[i]
  691. );
  692. }
  693. }
  694. html += '</tr>';
  695. }
  696. html += '</thead>';
  697. return html;
  698. };
  699. // Add table caption
  700. if (options.exporting.tableCaption !== false) {
  701. html += '<caption class="highcharts-table-caption">' + pick(
  702. options.exporting.tableCaption,
  703. (
  704. options.title.text ?
  705. htmlencode(options.title.text) :
  706. 'Chart'
  707. )) +
  708. '</caption>';
  709. }
  710. // Find longest row
  711. for (var i = 0, len = rows.length; i < len; ++i) {
  712. if (rows[i].length > rowLength) {
  713. rowLength = rows[i].length;
  714. }
  715. }
  716. // Add header
  717. html += getTableHeaderHTML(
  718. topHeaders,
  719. subHeaders,
  720. Math.max(rowLength, subHeaders.length)
  721. );
  722. // Transform the rows to HTML
  723. html += '<tbody>';
  724. each(rows, function (row) {
  725. html += '<tr>';
  726. for (var j = 0; j < rowLength; j++) {
  727. // Make first column a header too. Especially important for
  728. // category axes, but also might make sense for datetime? Should
  729. // await user feedback on this.
  730. html += getCellHTMLFromValue(
  731. j ? 'td' : 'th',
  732. null,
  733. j ? '' : 'scope="row"',
  734. row[j]
  735. );
  736. }
  737. html += '</tr>';
  738. });
  739. html += '</tbody></table>';
  740. return html;
  741. };
  742. /**
  743. * File download using download attribute if supported.
  744. *
  745. * @private
  746. */
  747. Highcharts.Chart.prototype.fileDownload = function (href, extension, content) {
  748. var a,
  749. blobObject,
  750. name;
  751. if (this.options.exporting.filename) {
  752. name = this.options.exporting.filename;
  753. } else if (this.title && this.title.textStr) {
  754. name = this.title.textStr.replace(/ /g, '-').toLowerCase();
  755. } else {
  756. name = 'chart';
  757. }
  758. // MS specific. Check this first because of bug with Edge (#76)
  759. if (win.Blob && win.navigator.msSaveOrOpenBlob) {
  760. // Falls to msSaveOrOpenBlob if download attribute is not supported
  761. blobObject = new win.Blob(
  762. ['\uFEFF' + content], // #7084
  763. { type: 'text/csv' }
  764. );
  765. win.navigator.msSaveOrOpenBlob(blobObject, name + '.' + extension);
  766. // Download attribute supported
  767. } else if (downloadAttrSupported) {
  768. a = doc.createElement('a');
  769. a.href = href;
  770. a.download = name + '.' + extension;
  771. this.container.appendChild(a); // #111
  772. a.click();
  773. a.remove();
  774. } else {
  775. Highcharts.error('The browser doesn\'t support downloading files');
  776. }
  777. };
  778. /**
  779. * Call this on click of 'Download CSV' button
  780. *
  781. * @private
  782. */
  783. Highcharts.Chart.prototype.downloadCSV = function () {
  784. var csv = this.getCSV(true);
  785. this.fileDownload(
  786. 'data:text/csv,\uFEFF' + encodeURIComponent(csv),
  787. 'csv',
  788. csv,
  789. 'text/csv'
  790. );
  791. };
  792. /**
  793. * Call this on click of 'Download XLS' button
  794. *
  795. * @private
  796. */
  797. Highcharts.Chart.prototype.downloadXLS = function () {
  798. var uri = 'data:application/vnd.ms-excel;base64,',
  799. template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" ' +
  800. 'xmlns:x="urn:schemas-microsoft-com:office:excel" ' +
  801. 'xmlns="http://www.w3.org/TR/REC-html40">' +
  802. '<head><!--[if gte mso 9]><xml><x:ExcelWorkbook>' +
  803. '<x:ExcelWorksheets><x:ExcelWorksheet>' +
  804. '<x:Name>Ark1</x:Name>' +
  805. '<x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>' +
  806. '</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook>' +
  807. '</xml><![endif]-->' +
  808. '<style>td{border:none;font-family: Calibri, sans-serif;} ' +
  809. '.number{mso-number-format:"0.00";} ' +
  810. '.text{ mso-number-format:"\@";}</style>' +
  811. '<meta name=ProgId content=Excel.Sheet>' +
  812. '<meta charset=UTF-8>' +
  813. '</head><body>' +
  814. this.getTable(true) +
  815. '</body></html>',
  816. base64 = function (s) {
  817. return win.btoa(unescape(encodeURIComponent(s))); // #50
  818. };
  819. this.fileDownload(
  820. uri + base64(template),
  821. 'xls',
  822. template,
  823. 'application/vnd.ms-excel'
  824. );
  825. };
  826. /**
  827. * Export-data module required. View the data in a table below the chart.
  828. */
  829. Highcharts.Chart.prototype.viewData = function () {
  830. if (!this.dataTableDiv) {
  831. this.dataTableDiv = doc.createElement('div');
  832. this.dataTableDiv.className = 'highcharts-data-table';
  833. // Insert after the chart container
  834. this.renderTo.parentNode.insertBefore(
  835. this.dataTableDiv,
  836. this.renderTo.nextSibling
  837. );
  838. }
  839. this.dataTableDiv.innerHTML = this.getTable();
  840. };
  841. /**
  842. * Experimental function to send a chart's config to the Cloud for editing.
  843. *
  844. * Limitations
  845. * - All functions (formatters and callbacks) are removed since they're not
  846. * JSON.
  847. *
  848. * @todo
  849. * - Let the Cloud throw a friendly warning about unsupported structures like
  850. * formatters.
  851. * - Dynamically updated charts probably fail, we need a generic
  852. * Chart.getOptions function that returns all non-default options. Should also
  853. * be used by the export module.
  854. */
  855. Highcharts.Chart.prototype.openInCloud = function () {
  856. var options,
  857. paramObj,
  858. params;
  859. // Recursively remove function callbacks
  860. function removeFunctions(ob) {
  861. Object.keys(ob).forEach(function (key) {
  862. if (typeof ob[key] === 'function') {
  863. delete ob[key];
  864. }
  865. if (Highcharts.isObject(ob[key])) { // object and not an array
  866. removeFunctions(ob[key]);
  867. }
  868. });
  869. }
  870. function openInCloud() {
  871. var form = doc.createElement('form');
  872. doc.body.appendChild(form);
  873. form.method = 'post';
  874. form.action = 'https://cloud-api.highcharts.com/openincloud';
  875. form.target = '_blank';
  876. var input = doc.createElement('input');
  877. input.type = 'hidden';
  878. input.name = 'chart';
  879. input.value = params;
  880. form.appendChild(input);
  881. form.submit();
  882. doc.body.removeChild(form);
  883. }
  884. options = Highcharts.merge(this.userOptions);
  885. removeFunctions(options);
  886. paramObj = {
  887. name: (options.title && options.title.text) || 'Chart title',
  888. options: options,
  889. settings: {
  890. constructor: 'Chart',
  891. dataProvider: {
  892. csv: this.getCSV()
  893. }
  894. }
  895. };
  896. params = JSON.stringify(paramObj);
  897. openInCloud();
  898. };
  899. // Add "Download CSV" to the exporting menu.
  900. var exportingOptions = Highcharts.getOptions().exporting;
  901. if (exportingOptions) {
  902. Highcharts.extend(exportingOptions.menuItemDefinitions, {
  903. downloadCSV: {
  904. textKey: 'downloadCSV',
  905. onclick: function () {
  906. this.downloadCSV();
  907. }
  908. },
  909. downloadXLS: {
  910. textKey: 'downloadXLS',
  911. onclick: function () {
  912. this.downloadXLS();
  913. }
  914. },
  915. viewData: {
  916. textKey: 'viewData',
  917. onclick: function () {
  918. this.viewData();
  919. }
  920. },
  921. openInCloud: {
  922. textKey: 'openInCloud',
  923. onclick: function () {
  924. this.openInCloud();
  925. }
  926. }
  927. });
  928. exportingOptions.buttons.contextButton.menuItems.push(
  929. 'separator',
  930. 'downloadCSV',
  931. 'downloadXLS',
  932. 'viewData',
  933. 'openInCloud'
  934. );
  935. }
  936. // Series specific
  937. if (seriesTypes.map) {
  938. seriesTypes.map.prototype.exportKey = 'name';
  939. }
  940. if (seriesTypes.mapbubble) {
  941. seriesTypes.mapbubble.prototype.exportKey = 'name';
  942. }
  943. if (seriesTypes.treemap) {
  944. seriesTypes.treemap.prototype.exportKey = 'name';
  945. }
  946. }(Highcharts));
  947. }));