Chart.js 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311
  1. /**
  2. * (c) 2010-2019 Torstein Honsi
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. /**
  7. * Callback for chart constructors.
  8. *
  9. * @callback Highcharts.ChartCallbackFunction
  10. *
  11. * @param {Highcharts.Chart} chart
  12. * Created chart.
  13. */
  14. /**
  15. * The chart title. The title has an `update` method that allows modifying the
  16. * options directly or indirectly via `chart.update`.
  17. *
  18. * @interface Highcharts.TitleObject
  19. * @extends Highcharts.SVGElement
  20. *//**
  21. * Modify options for the title.
  22. *
  23. * @function Highcharts.TitleObject#update
  24. *
  25. * @param {Highcharts.TitleOptions} titleOptions
  26. * Options to modify.
  27. *
  28. * @param {boolean} [redraw=true]
  29. * Whether to redraw the chart after the title is altered. If doing more
  30. * operations on the chart, it is a good idea to set redraw to false and
  31. * call {@link Chart#redraw} after.
  32. */
  33. /**
  34. * The chart subtitle. The subtitle has an `update` method that
  35. * allows modifying the options directly or indirectly via
  36. * `chart.update`.
  37. *
  38. * @interface Highcharts.SubtitleObject
  39. * @extends Highcharts.SVGElement
  40. *//**
  41. * Modify options for the subtitle.
  42. *
  43. * @function Highcharts.SubtitleObject#update
  44. *
  45. * @param {Highcharts.SubtitleOptions} subtitleOptions
  46. * Options to modify.
  47. *
  48. * @param {boolean} [redraw=true]
  49. * Whether to redraw the chart after the subtitle is altered. If doing
  50. * more operations on the chart, it is a good idea to set redraw to false
  51. * and call {@link Chart#redraw} after.
  52. */
  53. 'use strict';
  54. import H from './Globals.js';
  55. import './Utilities.js';
  56. import './Axis.js';
  57. import './Legend.js';
  58. import './Options.js';
  59. import './Pointer.js';
  60. var addEvent = H.addEvent,
  61. animate = H.animate,
  62. animObject = H.animObject,
  63. attr = H.attr,
  64. doc = H.doc,
  65. Axis = H.Axis, // @todo add as requirement
  66. createElement = H.createElement,
  67. defaultOptions = H.defaultOptions,
  68. discardElement = H.discardElement,
  69. charts = H.charts,
  70. css = H.css,
  71. defined = H.defined,
  72. extend = H.extend,
  73. find = H.find,
  74. fireEvent = H.fireEvent,
  75. isNumber = H.isNumber,
  76. isObject = H.isObject,
  77. isString = H.isString,
  78. Legend = H.Legend, // @todo add as requirement
  79. marginNames = H.marginNames,
  80. merge = H.merge,
  81. objectEach = H.objectEach,
  82. Pointer = H.Pointer, // @todo add as requirement
  83. pick = H.pick,
  84. pInt = H.pInt,
  85. removeEvent = H.removeEvent,
  86. seriesTypes = H.seriesTypes,
  87. splat = H.splat,
  88. syncTimeout = H.syncTimeout,
  89. win = H.win;
  90. /**
  91. * The Chart class. The recommended constructor is {@link Highcharts#chart}.
  92. *
  93. * @example
  94. * var chart = Highcharts.chart('container', {
  95. * title: {
  96. * text: 'My chart'
  97. * },
  98. * series: [{
  99. * data: [1, 3, 2, 4]
  100. * }]
  101. * })
  102. *
  103. * @class
  104. * @name Highcharts.Chart
  105. *
  106. * @param {string|Highcharts.HTMLDOMElement} [renderTo]
  107. * The DOM element to render to, or its id.
  108. *
  109. * @param {Highcharts.Options} options
  110. * The chart options structure.
  111. *
  112. * @param {Highcharts.ChartCallbackFunction} [callback]
  113. * Function to run when the chart has loaded and and all external images
  114. * are loaded. Defining a
  115. * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
  116. * handler is equivalent.
  117. */
  118. var Chart = H.Chart = function () {
  119. this.getArgs.apply(this, arguments);
  120. };
  121. /**
  122. * Factory function for basic charts.
  123. *
  124. * @example
  125. * // Render a chart in to div#container
  126. * var chart = Highcharts.chart('container', {
  127. * title: {
  128. * text: 'My chart'
  129. * },
  130. * series: [{
  131. * data: [1, 3, 2, 4]
  132. * }]
  133. * });
  134. *
  135. * @function Highcharts.chart
  136. *
  137. * @param {string|Highcharts.HTMLDOMElement} [renderTo]
  138. * The DOM element to render to, or its id.
  139. *
  140. * @param {Highcharts.Options} options
  141. * The chart options structure.
  142. *
  143. * @param {Highcharts.ChartCallbackFunction} [callback]
  144. * Function to run when the chart has loaded and and all external images
  145. * are loaded. Defining a
  146. * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
  147. * handler is equivalent.
  148. *
  149. * @return {Highcharts.Chart}
  150. * Returns the Chart object.
  151. */
  152. H.chart = function (a, b, c) {
  153. return new Chart(a, b, c);
  154. };
  155. extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
  156. // Hook for adding callbacks in modules
  157. callbacks: [],
  158. /**
  159. * Handle the arguments passed to the constructor.
  160. *
  161. * @private
  162. * @function Highcharts.Chart#getArgs
  163. *
  164. * @param {...Array<*>} arguments
  165. * All arguments for the constructor.
  166. *
  167. * @return {Array<*>}
  168. * Passed arguments without renderTo.
  169. *
  170. * @fires Highcharts.Chart#event:init
  171. * @fires Highcharts.Chart#event:afterInit
  172. */
  173. getArgs: function () {
  174. var args = [].slice.call(arguments);
  175. // Remove the optional first argument, renderTo, and
  176. // set it on this.
  177. if (isString(args[0]) || args[0].nodeName) {
  178. this.renderTo = args.shift();
  179. }
  180. this.init(args[0], args[1]);
  181. },
  182. /**
  183. * Overridable function that initializes the chart. The constructor's
  184. * arguments are passed on directly.
  185. *
  186. * @function Highcharts.Chart#init
  187. *
  188. * @param {Highcharts.Options} userOptions
  189. * Custom options.
  190. *
  191. * @param {Function} [callback]
  192. * Function to run when the chart has loaded and and all external
  193. * images are loaded.
  194. *
  195. * @fires Highcharts.Chart#event:init
  196. * @fires Highcharts.Chart#event:afterInit
  197. */
  198. init: function (userOptions, callback) {
  199. // Handle regular options
  200. var options,
  201. type,
  202. // skip merging data points to increase performance
  203. seriesOptions = userOptions.series,
  204. userPlotOptions = userOptions.plotOptions || {};
  205. // Fire the event with a default function
  206. fireEvent(this, 'init', { args: arguments }, function () {
  207. userOptions.series = null;
  208. options = merge(defaultOptions, userOptions); // do the merge
  209. // Override (by copy of user options) or clear tooltip options
  210. // in chart.options.plotOptions (#6218)
  211. for (type in options.plotOptions) {
  212. options.plotOptions[type].tooltip = (
  213. userPlotOptions[type] &&
  214. merge(userPlotOptions[type].tooltip) // override by copy
  215. ) || undefined; // or clear
  216. }
  217. // User options have higher priority than default options
  218. // (#6218). In case of exporting: path is changed
  219. options.tooltip.userOptions = (
  220. userOptions.chart &&
  221. userOptions.chart.forExport &&
  222. userOptions.tooltip.userOptions
  223. ) || userOptions.tooltip;
  224. // set back the series data
  225. options.series = userOptions.series = seriesOptions;
  226. this.userOptions = userOptions;
  227. var optionsChart = options.chart;
  228. var chartEvents = optionsChart.events;
  229. this.margin = [];
  230. this.spacing = [];
  231. // Pixel data bounds for touch zoom
  232. this.bounds = { h: {}, v: {} };
  233. // An array of functions that returns labels that should be
  234. // considered for anti-collision
  235. this.labelCollectors = [];
  236. this.callback = callback;
  237. this.isResizing = 0;
  238. /**
  239. * The options structure for the chart. It contains members for
  240. * the sub elements like series, legend, tooltip etc.
  241. *
  242. * @name Highcharts.Chart#options
  243. * @type {Highcharts.Options}
  244. */
  245. this.options = options;
  246. /**
  247. * All the axes in the chart.
  248. *
  249. * @see Highcharts.Chart.xAxis
  250. * @see Highcharts.Chart.yAxis
  251. *
  252. * @name Highcharts.Chart#axes
  253. * @type {Array<Highcharts.Axis>}
  254. */
  255. this.axes = [];
  256. /**
  257. * All the current series in the chart.
  258. *
  259. * @name Highcharts.Chart#series
  260. * @type {Array<Highcharts.Series>}
  261. */
  262. this.series = [];
  263. /**
  264. * The `Time` object associated with the chart. Since v6.0.5,
  265. * time settings can be applied individually for each chart. If
  266. * no individual settings apply, the `Time` object is shared by
  267. * all instances.
  268. *
  269. * @name Highcharts.Chart#time
  270. * @type {Highcharts.Time}
  271. */
  272. this.time =
  273. userOptions.time && Object.keys(userOptions.time).length ?
  274. new H.Time(userOptions.time) :
  275. H.time;
  276. /**
  277. * Whether the chart is in styled mode, meaning all presentatinoal
  278. * attributes are avoided.
  279. *
  280. * @name Highcharts.Chart#styledMode
  281. * @type {boolean}
  282. */
  283. this.styledMode = optionsChart.styledMode;
  284. this.hasCartesianSeries = optionsChart.showAxes;
  285. var chart = this;
  286. // Add the chart to the global lookup
  287. chart.index = charts.length;
  288. charts.push(chart);
  289. H.chartCount++;
  290. // Chart event handlers
  291. if (chartEvents) {
  292. objectEach(chartEvents, function (event, eventType) {
  293. addEvent(chart, eventType, event);
  294. });
  295. }
  296. /**
  297. * A collection of the X axes in the chart.
  298. *
  299. * @name Highcharts.Chart#xAxis
  300. * @type {Array<Highcharts.Axis>}
  301. */
  302. chart.xAxis = [];
  303. /**
  304. * A collection of the Y axes in the chart.
  305. *
  306. * @name Highcharts.Chart#yAxis
  307. * @type {Array<Highcharts.Axis>}
  308. *
  309. * @todo
  310. * Make events official: Fire the event `afterInit`.
  311. */
  312. chart.yAxis = [];
  313. chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
  314. // Fire after init but before first render, before axes and series
  315. // have been initialized.
  316. fireEvent(chart, 'afterInit');
  317. chart.firstRender();
  318. });
  319. },
  320. /**
  321. * Internal function to unitialize an individual series.
  322. *
  323. * @private
  324. * @function Highcharts.Chart#initSeries
  325. *
  326. * @param {Highcharts.ChartOptions} options
  327. *
  328. * @return {Highcharts.Series}
  329. */
  330. initSeries: function (options) {
  331. var chart = this,
  332. optionsChart = chart.options.chart,
  333. type = (
  334. options.type ||
  335. optionsChart.type ||
  336. optionsChart.defaultSeriesType
  337. ),
  338. series,
  339. Constr = seriesTypes[type];
  340. // No such series type
  341. if (!Constr) {
  342. H.error(17, true, chart);
  343. }
  344. series = new Constr();
  345. series.init(this, options);
  346. return series;
  347. },
  348. /**
  349. * Order all series above a given index. When series are added and ordered
  350. * by configuration, only the last series is handled (#248, #1123, #2456,
  351. * #6112). This function is called on series initialization and destroy.
  352. *
  353. * @private
  354. * @function Highcharts.Series#orderSeries
  355. *
  356. * @param {number} fromIndex
  357. * If this is given, only the series above this index are handled.
  358. */
  359. orderSeries: function (fromIndex) {
  360. var series = this.series,
  361. i = fromIndex || 0;
  362. for (; i < series.length; i++) {
  363. if (series[i]) {
  364. series[i].index = i;
  365. series[i].name = series[i].getName();
  366. }
  367. }
  368. },
  369. /**
  370. * Check whether a given point is within the plot area.
  371. *
  372. * @function Highcharts.Chart#isInsidePlot
  373. *
  374. * @param {number} plotX
  375. * Pixel x relative to the plot area.
  376. *
  377. * @param {number} plotY
  378. * Pixel y relative to the plot area.
  379. *
  380. * @param {boolean} inverted
  381. * Whether the chart is inverted.
  382. *
  383. * @return {boolean}
  384. * Returns true if the given point is inside the plot area.
  385. */
  386. isInsidePlot: function (plotX, plotY, inverted) {
  387. var x = inverted ? plotY : plotX,
  388. y = inverted ? plotX : plotY;
  389. return x >= 0 &&
  390. x <= this.plotWidth &&
  391. y >= 0 &&
  392. y <= this.plotHeight;
  393. },
  394. /**
  395. * Redraw the chart after changes have been done to the data, axis extremes
  396. * chart size or chart elements. All methods for updating axes, series or
  397. * points have a parameter for redrawing the chart. This is `true` by
  398. * default. But in many cases you want to do more than one operation on the
  399. * chart before redrawing, for example add a number of points. In those
  400. * cases it is a waste of resources to redraw the chart for each new point
  401. * added. So you add the points and call `chart.redraw()` after.
  402. *
  403. * @function Highcharts.Chart#redraw
  404. *
  405. * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
  406. * If or how to apply animation to the redraw.
  407. *
  408. * @fires Highcharts.Chart#event:afterSetExtremes
  409. * @fires Highcharts.Chart#event:beforeRedraw
  410. * @fires Highcharts.Chart#event:predraw
  411. * @fires Highcharts.Chart#event:redraw
  412. * @fires Highcharts.Chart#event:render
  413. * @fires Highcharts.Chart#event:updatedData
  414. */
  415. redraw: function (animation) {
  416. fireEvent(this, 'beforeRedraw');
  417. var chart = this,
  418. axes = chart.axes,
  419. series = chart.series,
  420. pointer = chart.pointer,
  421. legend = chart.legend,
  422. legendUserOptions = chart.userOptions.legend,
  423. redrawLegend = chart.isDirtyLegend,
  424. hasStackedSeries,
  425. hasDirtyStacks,
  426. hasCartesianSeries = chart.hasCartesianSeries,
  427. isDirtyBox = chart.isDirtyBox,
  428. i,
  429. serie,
  430. renderer = chart.renderer,
  431. isHiddenChart = renderer.isHidden(),
  432. afterRedraw = [];
  433. // Handle responsive rules, not only on resize (#6130)
  434. if (chart.setResponsive) {
  435. chart.setResponsive(false);
  436. }
  437. H.setAnimation(animation, chart);
  438. if (isHiddenChart) {
  439. chart.temporaryDisplay();
  440. }
  441. // Adjust title layout (reflow multiline text)
  442. chart.layOutTitles();
  443. // link stacked series
  444. i = series.length;
  445. while (i--) {
  446. serie = series[i];
  447. if (serie.options.stacking) {
  448. hasStackedSeries = true;
  449. if (serie.isDirty) {
  450. hasDirtyStacks = true;
  451. break;
  452. }
  453. }
  454. }
  455. if (hasDirtyStacks) { // mark others as dirty
  456. i = series.length;
  457. while (i--) {
  458. serie = series[i];
  459. if (serie.options.stacking) {
  460. serie.isDirty = true;
  461. }
  462. }
  463. }
  464. // Handle updated data in the series
  465. series.forEach(function (serie) {
  466. if (serie.isDirty) {
  467. if (serie.options.legendType === 'point') {
  468. if (serie.updateTotals) {
  469. serie.updateTotals();
  470. }
  471. redrawLegend = true;
  472. } else if (
  473. legendUserOptions &&
  474. (
  475. legendUserOptions.labelFormatter ||
  476. legendUserOptions.labelFormat
  477. )
  478. ) {
  479. redrawLegend = true; // #2165
  480. }
  481. }
  482. if (serie.isDirtyData) {
  483. fireEvent(serie, 'updatedData');
  484. }
  485. });
  486. // handle added or removed series
  487. if (redrawLegend && legend && legend.options.enabled) {
  488. // draw legend graphics
  489. legend.render();
  490. chart.isDirtyLegend = false;
  491. }
  492. // reset stacks
  493. if (hasStackedSeries) {
  494. chart.getStacks();
  495. }
  496. if (hasCartesianSeries) {
  497. // set axes scales
  498. axes.forEach(function (axis) {
  499. axis.updateNames();
  500. // Update categories in a Gantt chart
  501. if (axis.updateYNames) {
  502. axis.updateYNames();
  503. }
  504. axis.setScale();
  505. });
  506. }
  507. chart.getMargins(); // #3098
  508. if (hasCartesianSeries) {
  509. // If one axis is dirty, all axes must be redrawn (#792, #2169)
  510. axes.forEach(function (axis) {
  511. if (axis.isDirty) {
  512. isDirtyBox = true;
  513. }
  514. });
  515. // redraw axes
  516. axes.forEach(function (axis) {
  517. // Fire 'afterSetExtremes' only if extremes are set
  518. var key = axis.min + ',' + axis.max;
  519. if (axis.extKey !== key) { // #821, #4452
  520. axis.extKey = key;
  521. // prevent a recursive call to chart.redraw() (#1119)
  522. afterRedraw.push(function () {
  523. fireEvent(
  524. axis,
  525. 'afterSetExtremes',
  526. extend(axis.eventArgs, axis.getExtremes())
  527. ); // #747, #751
  528. delete axis.eventArgs;
  529. });
  530. }
  531. if (isDirtyBox || hasStackedSeries) {
  532. axis.redraw();
  533. }
  534. });
  535. }
  536. // the plot areas size has changed
  537. if (isDirtyBox) {
  538. chart.drawChartBox();
  539. }
  540. // Fire an event before redrawing series, used by the boost module to
  541. // clear previous series renderings.
  542. fireEvent(chart, 'predraw');
  543. // redraw affected series
  544. series.forEach(function (serie) {
  545. if ((isDirtyBox || serie.isDirty) && serie.visible) {
  546. serie.redraw();
  547. }
  548. // Set it here, otherwise we will have unlimited 'updatedData' calls
  549. // for a hidden series after setData(). Fixes #6012
  550. serie.isDirtyData = false;
  551. });
  552. // move tooltip or reset
  553. if (pointer) {
  554. pointer.reset(true);
  555. }
  556. // redraw if canvas
  557. renderer.draw();
  558. // Fire the events
  559. fireEvent(chart, 'redraw');
  560. fireEvent(chart, 'render');
  561. if (isHiddenChart) {
  562. chart.temporaryDisplay(true);
  563. }
  564. // Fire callbacks that are put on hold until after the redraw
  565. afterRedraw.forEach(function (callback) {
  566. callback.call();
  567. });
  568. },
  569. /**
  570. * Get an axis, series or point object by `id` as given in the configuration
  571. * options. Returns `undefined` if no item is found.
  572. *
  573. * @sample highcharts/plotoptions/series-id/
  574. * Get series by id
  575. *
  576. * @function Highcharts.Chart#get
  577. *
  578. * @param {string} id
  579. * The id as given in the configuration options.
  580. *
  581. * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
  582. * The retrieved item.
  583. */
  584. get: function (id) {
  585. var ret,
  586. series = this.series,
  587. i;
  588. function itemById(item) {
  589. return item.id === id || (item.options && item.options.id === id);
  590. }
  591. ret =
  592. // Search axes
  593. find(this.axes, itemById) ||
  594. // Search series
  595. find(this.series, itemById);
  596. // Search points
  597. for (i = 0; !ret && i < series.length; i++) {
  598. ret = find(series[i].points || [], itemById);
  599. }
  600. return ret;
  601. },
  602. /**
  603. * Create the Axis instances based on the config options.
  604. *
  605. * @private
  606. * @function Highcharts.Chart#getAxes
  607. *
  608. * @fires Highcharts.Chart#event:afterGetAxes
  609. * @fires Highcharts.Chart#event:getAxes
  610. */
  611. getAxes: function () {
  612. var chart = this,
  613. options = this.options,
  614. xAxisOptions = options.xAxis = splat(options.xAxis || {}),
  615. yAxisOptions = options.yAxis = splat(options.yAxis || {}),
  616. optionsArray;
  617. fireEvent(this, 'getAxes');
  618. // make sure the options are arrays and add some members
  619. xAxisOptions.forEach(function (axis, i) {
  620. axis.index = i;
  621. axis.isX = true;
  622. });
  623. yAxisOptions.forEach(function (axis, i) {
  624. axis.index = i;
  625. });
  626. // concatenate all axis options into one array
  627. optionsArray = xAxisOptions.concat(yAxisOptions);
  628. optionsArray.forEach(function (axisOptions) {
  629. new Axis(chart, axisOptions); // eslint-disable-line no-new
  630. });
  631. fireEvent(this, 'afterGetAxes');
  632. },
  633. /**
  634. * Returns an array of all currently selected points in the chart. Points
  635. * can be selected by clicking or programmatically by the
  636. * {@link Highcharts.Point#select}
  637. * function.
  638. *
  639. * @sample highcharts/plotoptions/series-allowpointselect-line/
  640. * Get selected points
  641. *
  642. * @function Highcharts.Chart#getSelectedPoints
  643. *
  644. * @return {Array<Highcharts.Point>}
  645. * The currently selected points.
  646. */
  647. getSelectedPoints: function () {
  648. var points = [];
  649. this.series.forEach(function (serie) {
  650. // For one-to-one points inspect series.data in order to retrieve
  651. // points outside the visible range (#6445). For grouped data,
  652. // inspect the generated series.points.
  653. points = points.concat(
  654. (serie[serie.hasGroupedData ? 'points' : 'data'] || []).filter(
  655. function (point) {
  656. return point.selected;
  657. }
  658. )
  659. );
  660. });
  661. return points;
  662. },
  663. /**
  664. * Returns an array of all currently selected series in the chart. Series
  665. * can be selected either programmatically by the
  666. * {@link Highcharts.Series#select}
  667. * function or by checking the checkbox next to the legend item if
  668. * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox)
  669. * is true.
  670. *
  671. * @sample highcharts/members/chart-getselectedseries/
  672. * Get selected series
  673. *
  674. * @function Highcharts.Chart#getSelectedSeries
  675. *
  676. * @return {Array<Highcharts.Series>}
  677. * The currently selected series.
  678. */
  679. getSelectedSeries: function () {
  680. return this.series.filter(function (serie) {
  681. return serie.selected;
  682. });
  683. },
  684. /**
  685. * Set a new title or subtitle for the chart.
  686. *
  687. * @sample highcharts/members/chart-settitle/
  688. * Set title text and styles
  689. *
  690. * @function Highcharts.Chart#setTitle
  691. *
  692. * @param {Highcharts.TitleOptions} titleOptions
  693. * New title options. The title text itself is set by the
  694. * `titleOptions.text` property.
  695. *
  696. * @param {Highcharts.SubtitleOptions} subtitleOptions
  697. * New subtitle options. The subtitle text itself is set by the
  698. * `subtitleOptions.text` property.
  699. *
  700. * @param {boolean} redraw
  701. * Whether to redraw the chart or wait for a later call to
  702. * `chart.redraw()`.
  703. */
  704. setTitle: function (titleOptions, subtitleOptions, redraw) {
  705. var chart = this,
  706. options = chart.options,
  707. styledMode = chart.styledMode,
  708. chartTitleOptions,
  709. chartSubtitleOptions;
  710. chartTitleOptions = options.title = merge(
  711. // Default styles
  712. !styledMode && {
  713. style: {
  714. color: '#333333',
  715. fontSize: options.isStock ? '16px' : '18px' // #2944
  716. }
  717. },
  718. options.title,
  719. titleOptions
  720. );
  721. chartSubtitleOptions = options.subtitle = merge(
  722. // Default styles
  723. !styledMode && {
  724. style: {
  725. color: '#666666'
  726. }
  727. },
  728. options.subtitle,
  729. subtitleOptions
  730. );
  731. // add title and subtitle
  732. /**
  733. * The chart title. The title has an `update` method that allows
  734. * modifying the options directly or indirectly via
  735. * `chart.update`.
  736. *
  737. * @sample highcharts/members/title-update/
  738. * Updating titles
  739. *
  740. * @name Highcharts.Chart#title
  741. * @type {Highcharts.TitleObject}
  742. */
  743. /**
  744. * The chart subtitle. The subtitle has an `update` method that
  745. * allows modifying the options directly or indirectly via
  746. * `chart.update`.
  747. *
  748. * @name Highcharts.Chart#subtitle
  749. * @type {Highcharts.SubtitleObject}
  750. */
  751. [
  752. ['title', titleOptions, chartTitleOptions],
  753. ['subtitle', subtitleOptions, chartSubtitleOptions]
  754. ].forEach(function (arr, i) {
  755. var name = arr[0],
  756. title = chart[name],
  757. titleOptions = arr[1],
  758. chartTitleOptions = arr[2];
  759. if (title && titleOptions) {
  760. chart[name] = title = title.destroy(); // remove old
  761. }
  762. if (chartTitleOptions && !title) {
  763. chart[name] = chart.renderer.text(
  764. chartTitleOptions.text,
  765. 0,
  766. 0,
  767. chartTitleOptions.useHTML
  768. )
  769. .attr({
  770. align: chartTitleOptions.align,
  771. 'class': 'highcharts-' + name,
  772. zIndex: chartTitleOptions.zIndex || 4
  773. })
  774. .add();
  775. // Update methods, shortcut to Chart.setTitle
  776. chart[name].update = function (o) {
  777. chart.setTitle(!i && o, i && o);
  778. };
  779. // Presentational
  780. if (!styledMode) {
  781. chart[name].css(chartTitleOptions.style);
  782. }
  783. }
  784. });
  785. chart.layOutTitles(redraw);
  786. },
  787. /**
  788. * Internal function to lay out the chart titles and cache the full offset
  789. * height for use in `getMargins`. The result is stored in
  790. * `this.titleOffset`.
  791. *
  792. * @private
  793. * @function Highcharts.Chart#layOutTitles
  794. *
  795. * @param {boolean} [redraw=true]
  796. */
  797. layOutTitles: function (redraw) {
  798. var titleOffset = 0,
  799. requiresDirtyBox,
  800. renderer = this.renderer,
  801. spacingBox = this.spacingBox;
  802. // Lay out the title and the subtitle respectively
  803. ['title', 'subtitle'].forEach(function (key) {
  804. var title = this[key],
  805. titleOptions = this.options[key],
  806. offset = key === 'title' ? -3 :
  807. // Floating subtitle (#6574)
  808. titleOptions.verticalAlign ? 0 : titleOffset + 2,
  809. titleSize;
  810. if (title) {
  811. if (!this.styledMode) {
  812. titleSize = titleOptions.style.fontSize;
  813. }
  814. titleSize = renderer.fontMetrics(titleSize, title).b;
  815. title
  816. .css({
  817. width: (titleOptions.width ||
  818. spacingBox.width + titleOptions.widthAdjust) + 'px'
  819. })
  820. .align(extend({
  821. y: offset + titleSize
  822. }, titleOptions), false, 'spacingBox');
  823. if (!titleOptions.floating && !titleOptions.verticalAlign) {
  824. titleOffset = Math.ceil(
  825. titleOffset +
  826. // Skip the cache for HTML (#3481)
  827. title.getBBox(titleOptions.useHTML).height
  828. );
  829. }
  830. }
  831. }, this);
  832. requiresDirtyBox = this.titleOffset !== titleOffset;
  833. this.titleOffset = titleOffset; // used in getMargins
  834. if (!this.isDirtyBox && requiresDirtyBox) {
  835. this.isDirtyBox = this.isDirtyLegend = requiresDirtyBox;
  836. // Redraw if necessary (#2719, #2744)
  837. if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
  838. this.redraw();
  839. }
  840. }
  841. },
  842. /**
  843. * Internal function to get the chart width and height according to options
  844. * and container size. Sets
  845. * {@link Chart.chartWidth} and
  846. * {@link Chart.chartHeight}.
  847. *
  848. * @function Highcharts.Chart#getChartSize
  849. */
  850. getChartSize: function () {
  851. var chart = this,
  852. optionsChart = chart.options.chart,
  853. widthOption = optionsChart.width,
  854. heightOption = optionsChart.height,
  855. renderTo = chart.renderTo;
  856. // Get inner width and height
  857. if (!defined(widthOption)) {
  858. chart.containerWidth = H.getStyle(renderTo, 'width');
  859. }
  860. if (!defined(heightOption)) {
  861. chart.containerHeight = H.getStyle(renderTo, 'height');
  862. }
  863. /**
  864. * The current pixel width of the chart.
  865. *
  866. * @name Highcharts.Chart#chartWidth
  867. * @type {number}
  868. */
  869. chart.chartWidth = Math.max( // #1393
  870. 0,
  871. widthOption || chart.containerWidth || 600 // #1460
  872. );
  873. /**
  874. * The current pixel height of the chart.
  875. *
  876. * @name Highcharts.Chart#chartHeight
  877. * @type {number}
  878. */
  879. chart.chartHeight = Math.max(
  880. 0,
  881. H.relativeLength(
  882. heightOption,
  883. chart.chartWidth
  884. ) ||
  885. (chart.containerHeight > 1 ? chart.containerHeight : 400)
  886. );
  887. },
  888. /**
  889. * If the renderTo element has no offsetWidth, most likely one or more of
  890. * its parents are hidden. Loop up the DOM tree to temporarily display the
  891. * parents, then save the original display properties, and when the true
  892. * size is retrieved, reset them. Used on first render and on redraws.
  893. *
  894. * @private
  895. * @function Highcharts.Chart#temporaryDisplay
  896. *
  897. * @param {boolean} revert
  898. * Revert to the saved original styles.
  899. */
  900. temporaryDisplay: function (revert) {
  901. var node = this.renderTo,
  902. tempStyle;
  903. if (!revert) {
  904. while (node && node.style) {
  905. // When rendering to a detached node, it needs to be temporarily
  906. // attached in order to read styling and bounding boxes (#5783,
  907. // #7024).
  908. if (!doc.body.contains(node) && !node.parentNode) {
  909. node.hcOrigDetached = true;
  910. doc.body.appendChild(node);
  911. }
  912. if (
  913. H.getStyle(node, 'display', false) === 'none' ||
  914. node.hcOricDetached
  915. ) {
  916. node.hcOrigStyle = {
  917. display: node.style.display,
  918. height: node.style.height,
  919. overflow: node.style.overflow
  920. };
  921. tempStyle = {
  922. display: 'block',
  923. overflow: 'hidden'
  924. };
  925. if (node !== this.renderTo) {
  926. tempStyle.height = 0;
  927. }
  928. H.css(node, tempStyle);
  929. // If it still doesn't have an offset width after setting
  930. // display to block, it probably has an !important priority
  931. // #2631, 6803
  932. if (!node.offsetWidth) {
  933. node.style.setProperty('display', 'block', 'important');
  934. }
  935. }
  936. node = node.parentNode;
  937. if (node === doc.body) {
  938. break;
  939. }
  940. }
  941. } else {
  942. while (node && node.style) {
  943. if (node.hcOrigStyle) {
  944. H.css(node, node.hcOrigStyle);
  945. delete node.hcOrigStyle;
  946. }
  947. if (node.hcOrigDetached) {
  948. doc.body.removeChild(node);
  949. node.hcOrigDetached = false;
  950. }
  951. node = node.parentNode;
  952. }
  953. }
  954. },
  955. /**
  956. * Set the {@link Chart.container|chart container's} class name, in
  957. * addition to `highcharts-container`.
  958. *
  959. * @function Highcharts.Chart#setClassName
  960. *
  961. * @param {string} className
  962. */
  963. setClassName: function (className) {
  964. this.container.className = 'highcharts-container ' + (className || '');
  965. },
  966. /**
  967. * Get the containing element, determine the size and create the inner
  968. * container div to hold the chart.
  969. *
  970. * @private
  971. * @function Highcharts.Chart#afterGetContainer
  972. *
  973. * @fires Highcharts.Chart#event:afterGetContainer
  974. */
  975. getContainer: function () {
  976. var chart = this,
  977. container,
  978. options = chart.options,
  979. optionsChart = options.chart,
  980. chartWidth,
  981. chartHeight,
  982. renderTo = chart.renderTo,
  983. indexAttrName = 'data-highcharts-chart',
  984. oldChartIndex,
  985. Ren,
  986. containerId = H.uniqueKey(),
  987. containerStyle,
  988. key;
  989. if (!renderTo) {
  990. chart.renderTo = renderTo = optionsChart.renderTo;
  991. }
  992. if (isString(renderTo)) {
  993. chart.renderTo = renderTo = doc.getElementById(renderTo);
  994. }
  995. // Display an error if the renderTo is wrong
  996. if (!renderTo) {
  997. H.error(13, true, chart);
  998. }
  999. // If the container already holds a chart, destroy it. The check for
  1000. // hasRendered is there because web pages that are saved to disk from
  1001. // the browser, will preserve the data-highcharts-chart attribute and
  1002. // the SVG contents, but not an interactive chart. So in this case,
  1003. // charts[oldChartIndex] will point to the wrong chart if any (#2609).
  1004. oldChartIndex = pInt(attr(renderTo, indexAttrName));
  1005. if (
  1006. isNumber(oldChartIndex) &&
  1007. charts[oldChartIndex] &&
  1008. charts[oldChartIndex].hasRendered
  1009. ) {
  1010. charts[oldChartIndex].destroy();
  1011. }
  1012. // Make a reference to the chart from the div
  1013. attr(renderTo, indexAttrName, chart.index);
  1014. // remove previous chart
  1015. renderTo.innerHTML = '';
  1016. // If the container doesn't have an offsetWidth, it has or is a child of
  1017. // a node that has display:none. We need to temporarily move it out to a
  1018. // visible state to determine the size, else the legend and tooltips
  1019. // won't render properly. The skipClone option is used in sparklines as
  1020. // a micro optimization, saving about 1-2 ms each chart.
  1021. if (!optionsChart.skipClone && !renderTo.offsetWidth) {
  1022. chart.temporaryDisplay();
  1023. }
  1024. // get the width and height
  1025. chart.getChartSize();
  1026. chartWidth = chart.chartWidth;
  1027. chartHeight = chart.chartHeight;
  1028. // Allow table cells and flex-boxes to shrink without the chart blocking
  1029. // them out (#6427)
  1030. css(renderTo, { overflow: 'hidden' });
  1031. // Create the inner container
  1032. if (!chart.styledMode) {
  1033. containerStyle = extend({
  1034. position: 'relative',
  1035. // needed for context menu (avoidscrollbars) and content
  1036. // overflow in IE
  1037. overflow: 'hidden',
  1038. width: chartWidth + 'px',
  1039. height: chartHeight + 'px',
  1040. textAlign: 'left',
  1041. lineHeight: 'normal', // #427
  1042. zIndex: 0, // #1072
  1043. '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
  1044. }, optionsChart.style);
  1045. }
  1046. /**
  1047. * The containing HTML element of the chart. The container is
  1048. * dynamically inserted into the element given as the `renderTo`
  1049. * parameter in the {@link Highcharts#chart} constructor.
  1050. *
  1051. * @name Highcharts.Chart#container
  1052. * @type {Highcharts.HTMLDOMElement}
  1053. */
  1054. container = createElement(
  1055. 'div',
  1056. {
  1057. id: containerId
  1058. },
  1059. containerStyle,
  1060. renderTo
  1061. );
  1062. chart.container = container;
  1063. // cache the cursor (#1650)
  1064. chart._cursor = container.style.cursor;
  1065. // Initialize the renderer
  1066. Ren = H[optionsChart.renderer] || H.Renderer;
  1067. /**
  1068. * The renderer instance of the chart. Each chart instance has only one
  1069. * associated renderer.
  1070. *
  1071. * @name Highcharts.Chart#renderer
  1072. * @type {Highcharts.SVGRenderer}
  1073. */
  1074. chart.renderer = new Ren(
  1075. container,
  1076. chartWidth,
  1077. chartHeight,
  1078. null,
  1079. optionsChart.forExport,
  1080. options.exporting && options.exporting.allowHTML,
  1081. chart.styledMode
  1082. );
  1083. chart.setClassName(optionsChart.className);
  1084. if (!chart.styledMode) {
  1085. chart.renderer.setStyle(optionsChart.style);
  1086. } else {
  1087. // Initialize definitions
  1088. for (key in options.defs) {
  1089. this.renderer.definition(options.defs[key]);
  1090. }
  1091. }
  1092. // Add a reference to the charts index
  1093. chart.renderer.chartIndex = chart.index;
  1094. fireEvent(this, 'afterGetContainer');
  1095. },
  1096. /**
  1097. * Calculate margins by rendering axis labels in a preliminary position.
  1098. * Title, subtitle and legend have already been rendered at this stage, but
  1099. * will be moved into their final positions.
  1100. *
  1101. * @private
  1102. * @function Highcharts.Chart#getMargins
  1103. *
  1104. * @param {boolean} skipAxes
  1105. *
  1106. * @fires Highcharts.Chart#event:getMargins
  1107. */
  1108. getMargins: function (skipAxes) {
  1109. var chart = this,
  1110. spacing = chart.spacing,
  1111. margin = chart.margin,
  1112. titleOffset = chart.titleOffset;
  1113. chart.resetMargins();
  1114. // Adjust for title and subtitle
  1115. if (titleOffset && !defined(margin[0])) {
  1116. chart.plotTop = Math.max(
  1117. chart.plotTop,
  1118. titleOffset + chart.options.title.margin + spacing[0]
  1119. );
  1120. }
  1121. // Adjust for legend
  1122. if (chart.legend && chart.legend.display) {
  1123. chart.legend.adjustMargins(margin, spacing);
  1124. }
  1125. fireEvent(this, 'getMargins');
  1126. if (!skipAxes) {
  1127. this.getAxisMargins();
  1128. }
  1129. },
  1130. /**
  1131. * @private
  1132. * @function Highcharts.Chart#getAxisMargins
  1133. */
  1134. getAxisMargins: function () {
  1135. var chart = this,
  1136. // [top, right, bottom, left]
  1137. axisOffset = chart.axisOffset = [0, 0, 0, 0],
  1138. margin = chart.margin;
  1139. // pre-render axes to get labels offset width
  1140. if (chart.hasCartesianSeries) {
  1141. chart.axes.forEach(function (axis) {
  1142. if (axis.visible) {
  1143. axis.getOffset();
  1144. }
  1145. });
  1146. }
  1147. // Add the axis offsets
  1148. marginNames.forEach(function (m, side) {
  1149. if (!defined(margin[side])) {
  1150. chart[m] += axisOffset[side];
  1151. }
  1152. });
  1153. chart.setChartSize();
  1154. },
  1155. /**
  1156. * Reflows the chart to its container. By default, the chart reflows
  1157. * automatically to its container following a `window.resize` event, as per
  1158. * the [chart.reflow](https://api.highcharts/highcharts/chart.reflow)
  1159. * option. However, there are no reliable events for div resize, so if the
  1160. * container is resized without a window resize event, this must be called
  1161. * explicitly.
  1162. *
  1163. * @sample highcharts/members/chart-reflow/
  1164. * Resize div and reflow
  1165. * @sample highcharts/chart/events-container/
  1166. * Pop up and reflow
  1167. *
  1168. * @function Highcharts.Chart#reflow
  1169. *
  1170. * @param {global.Event} [e]
  1171. * Event arguments. Used primarily when the function is called
  1172. * internally as a response to window resize.
  1173. */
  1174. reflow: function (e) {
  1175. var chart = this,
  1176. optionsChart = chart.options.chart,
  1177. renderTo = chart.renderTo,
  1178. hasUserSize = (
  1179. defined(optionsChart.width) &&
  1180. defined(optionsChart.height)
  1181. ),
  1182. width = optionsChart.width || H.getStyle(renderTo, 'width'),
  1183. height = optionsChart.height || H.getStyle(renderTo, 'height'),
  1184. target = e ? e.target : win;
  1185. // Width and height checks for display:none. Target is doc in IE8 and
  1186. // Opera, win in Firefox, Chrome and IE9.
  1187. if (
  1188. !hasUserSize &&
  1189. !chart.isPrinting &&
  1190. width &&
  1191. height &&
  1192. (target === win || target === doc)
  1193. ) {
  1194. if (
  1195. width !== chart.containerWidth ||
  1196. height !== chart.containerHeight
  1197. ) {
  1198. H.clearTimeout(chart.reflowTimeout);
  1199. // When called from window.resize, e is set, else it's called
  1200. // directly (#2224)
  1201. chart.reflowTimeout = syncTimeout(function () {
  1202. // Set size, it may have been destroyed in the meantime
  1203. // (#1257)
  1204. if (chart.container) {
  1205. chart.setSize(undefined, undefined, false);
  1206. }
  1207. }, e ? 100 : 0);
  1208. }
  1209. chart.containerWidth = width;
  1210. chart.containerHeight = height;
  1211. }
  1212. },
  1213. /**
  1214. * Toggle the event handlers necessary for auto resizing, depending on the
  1215. * `chart.reflow` option.
  1216. *
  1217. * @private
  1218. * @function Highcharts.Chart#setReflow
  1219. *
  1220. * @param {boolean} reflow
  1221. */
  1222. setReflow: function (reflow) {
  1223. var chart = this;
  1224. if (reflow !== false && !this.unbindReflow) {
  1225. this.unbindReflow = addEvent(win, 'resize', function (e) {
  1226. chart.reflow(e);
  1227. });
  1228. addEvent(this, 'destroy', this.unbindReflow);
  1229. } else if (reflow === false && this.unbindReflow) {
  1230. // Unbind and unset
  1231. this.unbindReflow = this.unbindReflow();
  1232. }
  1233. // The following will add listeners to re-fit the chart before and after
  1234. // printing (#2284). However it only works in WebKit. Should have worked
  1235. // in Firefox, but not supported in IE.
  1236. /*
  1237. if (win.matchMedia) {
  1238. win.matchMedia('print').addListener(function reflow() {
  1239. chart.reflow();
  1240. });
  1241. }
  1242. //*/
  1243. },
  1244. /**
  1245. * Resize the chart to a given width and height. In order to set the width
  1246. * only, the height argument may be skipped. To set the height only, pass
  1247. * `undefined` for the width.
  1248. *
  1249. * @sample highcharts/members/chart-setsize-button/
  1250. * Test resizing from buttons
  1251. * @sample highcharts/members/chart-setsize-jquery-resizable/
  1252. * Add a jQuery UI resizable
  1253. * @sample stock/members/chart-setsize/
  1254. * Highstock with UI resizable
  1255. *
  1256. * @function Highcharts.Chart#setSize
  1257. *
  1258. * @param {number|null} [width]
  1259. * The new pixel width of the chart. Since v4.2.6, the argument can
  1260. * be `undefined` in order to preserve the current value (when
  1261. * setting height only), or `null` to adapt to the width of the
  1262. * containing element.
  1263. *
  1264. * @param {number|null} [height]
  1265. * The new pixel height of the chart. Since v4.2.6, the argument can
  1266. * be `undefined` in order to preserve the current value, or `null`
  1267. * in order to adapt to the height of the containing element.
  1268. *
  1269. * @param {Highcharts.AnimationOptionsObject} [animation=true]
  1270. * Whether and how to apply animation.
  1271. *
  1272. * @fires Highcharts.Chart#event:endResize
  1273. * @fires Highcharts.Chart#event:resize
  1274. */
  1275. setSize: function (width, height, animation) {
  1276. var chart = this,
  1277. renderer = chart.renderer,
  1278. globalAnimation;
  1279. // Handle the isResizing counter
  1280. chart.isResizing += 1;
  1281. // set the animation for the current process
  1282. H.setAnimation(animation, chart);
  1283. chart.oldChartHeight = chart.chartHeight;
  1284. chart.oldChartWidth = chart.chartWidth;
  1285. if (width !== undefined) {
  1286. chart.options.chart.width = width;
  1287. }
  1288. if (height !== undefined) {
  1289. chart.options.chart.height = height;
  1290. }
  1291. chart.getChartSize();
  1292. // Resize the container with the global animation applied if enabled
  1293. // (#2503)
  1294. if (!chart.styledMode) {
  1295. globalAnimation = renderer.globalAnimation;
  1296. (globalAnimation ? animate : css)(chart.container, {
  1297. width: chart.chartWidth + 'px',
  1298. height: chart.chartHeight + 'px'
  1299. }, globalAnimation);
  1300. }
  1301. chart.setChartSize(true);
  1302. renderer.setSize(chart.chartWidth, chart.chartHeight, animation);
  1303. // handle axes
  1304. chart.axes.forEach(function (axis) {
  1305. axis.isDirty = true;
  1306. axis.setScale();
  1307. });
  1308. chart.isDirtyLegend = true; // force legend redraw
  1309. chart.isDirtyBox = true; // force redraw of plot and chart border
  1310. chart.layOutTitles(); // #2857
  1311. chart.getMargins();
  1312. chart.redraw(animation);
  1313. chart.oldChartHeight = null;
  1314. fireEvent(chart, 'resize');
  1315. // Fire endResize and set isResizing back. If animation is disabled,
  1316. // fire without delay
  1317. syncTimeout(function () {
  1318. if (chart) {
  1319. fireEvent(chart, 'endResize', null, function () {
  1320. chart.isResizing -= 1;
  1321. });
  1322. }
  1323. }, animObject(globalAnimation).duration);
  1324. },
  1325. /**
  1326. * Set the public chart properties. This is done before and after the
  1327. * pre-render to determine margin sizes.
  1328. *
  1329. * @private
  1330. * @function Highcharts.Chart#setChartSize
  1331. *
  1332. * @param {boolean} skipAxes
  1333. *
  1334. * @fires Highcharts.Chart#event:afterSetChartSize
  1335. */
  1336. setChartSize: function (skipAxes) {
  1337. var chart = this,
  1338. inverted = chart.inverted,
  1339. renderer = chart.renderer,
  1340. chartWidth = chart.chartWidth,
  1341. chartHeight = chart.chartHeight,
  1342. optionsChart = chart.options.chart,
  1343. spacing = chart.spacing,
  1344. clipOffset = chart.clipOffset,
  1345. clipX,
  1346. clipY,
  1347. plotLeft,
  1348. plotTop,
  1349. plotWidth,
  1350. plotHeight,
  1351. plotBorderWidth;
  1352. /**
  1353. * The current left position of the plot area in pixels.
  1354. *
  1355. * @name Highcharts.Chart#plotLeft
  1356. * @type {number}
  1357. */
  1358. chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
  1359. /**
  1360. * The current top position of the plot area in pixels.
  1361. *
  1362. * @name Highcharts.Chart#plotTop
  1363. * @type {number}
  1364. */
  1365. chart.plotTop = plotTop = Math.round(chart.plotTop);
  1366. /**
  1367. * The current width of the plot area in pixels.
  1368. *
  1369. * @name Highcharts.Chart#plotWidth
  1370. * @type {number}
  1371. */
  1372. chart.plotWidth = plotWidth = Math.max(
  1373. 0,
  1374. Math.round(chartWidth - plotLeft - chart.marginRight)
  1375. );
  1376. /**
  1377. * The current height of the plot area in pixels.
  1378. *
  1379. * @name Highcharts.Chart#plotHeight
  1380. * @type {number}
  1381. */
  1382. chart.plotHeight = plotHeight = Math.max(
  1383. 0,
  1384. Math.round(chartHeight - plotTop - chart.marginBottom)
  1385. );
  1386. chart.plotSizeX = inverted ? plotHeight : plotWidth;
  1387. chart.plotSizeY = inverted ? plotWidth : plotHeight;
  1388. chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
  1389. // Set boxes used for alignment
  1390. chart.spacingBox = renderer.spacingBox = {
  1391. x: spacing[3],
  1392. y: spacing[0],
  1393. width: chartWidth - spacing[3] - spacing[1],
  1394. height: chartHeight - spacing[0] - spacing[2]
  1395. };
  1396. chart.plotBox = renderer.plotBox = {
  1397. x: plotLeft,
  1398. y: plotTop,
  1399. width: plotWidth,
  1400. height: plotHeight
  1401. };
  1402. plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2);
  1403. clipX = Math.ceil(Math.max(plotBorderWidth, clipOffset[3]) / 2);
  1404. clipY = Math.ceil(Math.max(plotBorderWidth, clipOffset[0]) / 2);
  1405. chart.clipBox = {
  1406. x: clipX,
  1407. y: clipY,
  1408. width: Math.floor(
  1409. chart.plotSizeX -
  1410. Math.max(plotBorderWidth, clipOffset[1]) / 2 -
  1411. clipX
  1412. ),
  1413. height: Math.max(
  1414. 0,
  1415. Math.floor(
  1416. chart.plotSizeY -
  1417. Math.max(plotBorderWidth, clipOffset[2]) / 2 -
  1418. clipY
  1419. )
  1420. )
  1421. };
  1422. if (!skipAxes) {
  1423. chart.axes.forEach(function (axis) {
  1424. axis.setAxisSize();
  1425. axis.setAxisTranslation();
  1426. });
  1427. }
  1428. fireEvent(chart, 'afterSetChartSize', { skipAxes: skipAxes });
  1429. },
  1430. /**
  1431. * Initial margins before auto size margins are applied.
  1432. *
  1433. * @private
  1434. * @function Highcharts.Chart#resetMargins
  1435. */
  1436. resetMargins: function () {
  1437. fireEvent(this, 'resetMargins');
  1438. var chart = this,
  1439. chartOptions = chart.options.chart;
  1440. // Create margin and spacing array
  1441. ['margin', 'spacing'].forEach(function splashArrays(target) {
  1442. var value = chartOptions[target],
  1443. values = isObject(value) ? value : [value, value, value, value];
  1444. [
  1445. 'Top',
  1446. 'Right',
  1447. 'Bottom',
  1448. 'Left'
  1449. ].forEach(function (sideName, side) {
  1450. chart[target][side] = pick(
  1451. chartOptions[target + sideName],
  1452. values[side]
  1453. );
  1454. });
  1455. });
  1456. // Set margin names like chart.plotTop, chart.plotLeft,
  1457. // chart.marginRight, chart.marginBottom.
  1458. marginNames.forEach(function (m, side) {
  1459. chart[m] = pick(chart.margin[side], chart.spacing[side]);
  1460. });
  1461. chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
  1462. chart.clipOffset = [0, 0, 0, 0];
  1463. },
  1464. /**
  1465. * Internal function to draw or redraw the borders and backgrounds for chart
  1466. * and plot area.
  1467. *
  1468. * @private
  1469. * @function Highcharts.Chart#drawChartBox
  1470. *
  1471. * @fires Highcharts.Chart#event:afterDrawChartBox
  1472. */
  1473. drawChartBox: function () {
  1474. var chart = this,
  1475. optionsChart = chart.options.chart,
  1476. renderer = chart.renderer,
  1477. chartWidth = chart.chartWidth,
  1478. chartHeight = chart.chartHeight,
  1479. chartBackground = chart.chartBackground,
  1480. plotBackground = chart.plotBackground,
  1481. plotBorder = chart.plotBorder,
  1482. chartBorderWidth,
  1483. styledMode = chart.styledMode,
  1484. plotBGImage = chart.plotBGImage,
  1485. chartBackgroundColor = optionsChart.backgroundColor,
  1486. plotBackgroundColor = optionsChart.plotBackgroundColor,
  1487. plotBackgroundImage = optionsChart.plotBackgroundImage,
  1488. mgn,
  1489. bgAttr,
  1490. plotLeft = chart.plotLeft,
  1491. plotTop = chart.plotTop,
  1492. plotWidth = chart.plotWidth,
  1493. plotHeight = chart.plotHeight,
  1494. plotBox = chart.plotBox,
  1495. clipRect = chart.clipRect,
  1496. clipBox = chart.clipBox,
  1497. verb = 'animate';
  1498. // Chart area
  1499. if (!chartBackground) {
  1500. chart.chartBackground = chartBackground = renderer.rect()
  1501. .addClass('highcharts-background')
  1502. .add();
  1503. verb = 'attr';
  1504. }
  1505. if (!styledMode) {
  1506. // Presentational
  1507. chartBorderWidth = optionsChart.borderWidth || 0;
  1508. mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
  1509. bgAttr = {
  1510. fill: chartBackgroundColor || 'none'
  1511. };
  1512. if (chartBorderWidth || chartBackground['stroke-width']) { // #980
  1513. bgAttr.stroke = optionsChart.borderColor;
  1514. bgAttr['stroke-width'] = chartBorderWidth;
  1515. }
  1516. chartBackground
  1517. .attr(bgAttr)
  1518. .shadow(optionsChart.shadow);
  1519. } else {
  1520. chartBorderWidth = mgn = chartBackground.strokeWidth();
  1521. }
  1522. chartBackground[verb]({
  1523. x: mgn / 2,
  1524. y: mgn / 2,
  1525. width: chartWidth - mgn - chartBorderWidth % 2,
  1526. height: chartHeight - mgn - chartBorderWidth % 2,
  1527. r: optionsChart.borderRadius
  1528. });
  1529. // Plot background
  1530. verb = 'animate';
  1531. if (!plotBackground) {
  1532. verb = 'attr';
  1533. chart.plotBackground = plotBackground = renderer.rect()
  1534. .addClass('highcharts-plot-background')
  1535. .add();
  1536. }
  1537. plotBackground[verb](plotBox);
  1538. if (!styledMode) {
  1539. // Presentational attributes for the background
  1540. plotBackground
  1541. .attr({
  1542. fill: plotBackgroundColor || 'none'
  1543. })
  1544. .shadow(optionsChart.plotShadow);
  1545. // Create the background image
  1546. if (plotBackgroundImage) {
  1547. if (!plotBGImage) {
  1548. chart.plotBGImage = renderer.image(
  1549. plotBackgroundImage,
  1550. plotLeft,
  1551. plotTop,
  1552. plotWidth,
  1553. plotHeight
  1554. ).add();
  1555. } else {
  1556. plotBGImage.animate(plotBox);
  1557. }
  1558. }
  1559. }
  1560. // Plot clip
  1561. if (!clipRect) {
  1562. chart.clipRect = renderer.clipRect(clipBox);
  1563. } else {
  1564. clipRect.animate({
  1565. width: clipBox.width,
  1566. height: clipBox.height
  1567. });
  1568. }
  1569. // Plot area border
  1570. verb = 'animate';
  1571. if (!plotBorder) {
  1572. verb = 'attr';
  1573. chart.plotBorder = plotBorder = renderer.rect()
  1574. .addClass('highcharts-plot-border')
  1575. .attr({
  1576. zIndex: 1 // Above the grid
  1577. })
  1578. .add();
  1579. }
  1580. if (!styledMode) {
  1581. // Presentational
  1582. plotBorder.attr({
  1583. stroke: optionsChart.plotBorderColor,
  1584. 'stroke-width': optionsChart.plotBorderWidth || 0,
  1585. fill: 'none'
  1586. });
  1587. }
  1588. plotBorder[verb](plotBorder.crisp({
  1589. x: plotLeft,
  1590. y: plotTop,
  1591. width: plotWidth,
  1592. height: plotHeight
  1593. }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative;
  1594. // reset
  1595. chart.isDirtyBox = false;
  1596. fireEvent(this, 'afterDrawChartBox');
  1597. },
  1598. /**
  1599. * Detect whether a certain chart property is needed based on inspecting its
  1600. * options and series. This mainly applies to the chart.inverted property,
  1601. * and in extensions to the chart.angular and chart.polar properties.
  1602. *
  1603. * @private
  1604. * @function Highcharts.Chart#propFromSeries
  1605. */
  1606. propFromSeries: function () {
  1607. var chart = this,
  1608. optionsChart = chart.options.chart,
  1609. klass,
  1610. seriesOptions = chart.options.series,
  1611. i,
  1612. value;
  1613. ['inverted', 'angular', 'polar'].forEach(function (key) {
  1614. // The default series type's class
  1615. klass = seriesTypes[optionsChart.type ||
  1616. optionsChart.defaultSeriesType];
  1617. // Get the value from available chart-wide properties
  1618. value =
  1619. optionsChart[key] || // It is set in the options
  1620. (klass && klass.prototype[key]); // The default series class
  1621. // requires it
  1622. // 4. Check if any the chart's series require it
  1623. i = seriesOptions && seriesOptions.length;
  1624. while (!value && i--) {
  1625. klass = seriesTypes[seriesOptions[i].type];
  1626. if (klass && klass.prototype[key]) {
  1627. value = true;
  1628. }
  1629. }
  1630. // Set the chart property
  1631. chart[key] = value;
  1632. });
  1633. },
  1634. /**
  1635. * Internal function to link two or more series together, based on the
  1636. * `linkedTo` option. This is done from `Chart.render`, and after
  1637. * `Chart.addSeries` and `Series.remove`.
  1638. *
  1639. * @private
  1640. * @function Highcharts.Chart#linkSeries
  1641. *
  1642. * @fires Highcharts.Chart#event:afterLinkSeries
  1643. */
  1644. linkSeries: function () {
  1645. var chart = this,
  1646. chartSeries = chart.series;
  1647. // Reset links
  1648. chartSeries.forEach(function (series) {
  1649. series.linkedSeries.length = 0;
  1650. });
  1651. // Apply new links
  1652. chartSeries.forEach(function (series) {
  1653. var linkedTo = series.options.linkedTo;
  1654. if (isString(linkedTo)) {
  1655. if (linkedTo === ':previous') {
  1656. linkedTo = chart.series[series.index - 1];
  1657. } else {
  1658. linkedTo = chart.get(linkedTo);
  1659. }
  1660. // #3341 avoid mutual linking
  1661. if (linkedTo && linkedTo.linkedParent !== series) {
  1662. linkedTo.linkedSeries.push(series);
  1663. series.linkedParent = linkedTo;
  1664. series.visible = pick(
  1665. series.options.visible,
  1666. linkedTo.options.visible,
  1667. series.visible
  1668. ); // #3879
  1669. }
  1670. }
  1671. });
  1672. fireEvent(this, 'afterLinkSeries');
  1673. },
  1674. /**
  1675. * Render series for the chart.
  1676. *
  1677. * @private
  1678. * @function Highcharts.Chart#renderSeries
  1679. */
  1680. renderSeries: function () {
  1681. this.series.forEach(function (serie) {
  1682. serie.translate();
  1683. serie.render();
  1684. });
  1685. },
  1686. /**
  1687. * Render labels for the chart.
  1688. *
  1689. * @private
  1690. * @function Highcharts.Chart#renderLabels
  1691. */
  1692. renderLabels: function () {
  1693. var chart = this,
  1694. labels = chart.options.labels;
  1695. if (labels.items) {
  1696. labels.items.forEach(function (label) {
  1697. var style = extend(labels.style, label.style),
  1698. x = pInt(style.left) + chart.plotLeft,
  1699. y = pInt(style.top) + chart.plotTop + 12;
  1700. // delete to prevent rewriting in IE
  1701. delete style.left;
  1702. delete style.top;
  1703. chart.renderer.text(
  1704. label.html,
  1705. x,
  1706. y
  1707. )
  1708. .attr({ zIndex: 2 })
  1709. .css(style)
  1710. .add();
  1711. });
  1712. }
  1713. },
  1714. /**
  1715. * Render all graphics for the chart. Runs internally on initialization.
  1716. *
  1717. * @private
  1718. * @function Highcharts.Chart#render
  1719. */
  1720. render: function () {
  1721. var chart = this,
  1722. axes = chart.axes,
  1723. renderer = chart.renderer,
  1724. options = chart.options,
  1725. correction = 0, // correction for X axis labels
  1726. tempWidth,
  1727. tempHeight,
  1728. redoHorizontal,
  1729. redoVertical;
  1730. // Title
  1731. chart.setTitle();
  1732. /**
  1733. * The overview of the chart's series.
  1734. *
  1735. * @name Highcharts.Chart#legend
  1736. * @type {Highcharts.Legend}
  1737. */
  1738. chart.legend = new Legend(chart, options.legend);
  1739. // Get stacks
  1740. if (chart.getStacks) {
  1741. chart.getStacks();
  1742. }
  1743. // Get chart margins
  1744. chart.getMargins(true);
  1745. chart.setChartSize();
  1746. // Record preliminary dimensions for later comparison
  1747. tempWidth = chart.plotWidth;
  1748. axes.some(function (axis) {
  1749. if (
  1750. axis.horiz &&
  1751. axis.visible &&
  1752. axis.options.labels.enabled &&
  1753. axis.series.length
  1754. ) {
  1755. // 21 is the most common correction for X axis labels
  1756. correction = 21;
  1757. return true;
  1758. }
  1759. });
  1760. // use Math.max to prevent negative plotHeight
  1761. chart.plotHeight = Math.max(chart.plotHeight - correction, 0);
  1762. tempHeight = chart.plotHeight;
  1763. // Get margins by pre-rendering axes
  1764. axes.forEach(function (axis) {
  1765. axis.setScale();
  1766. });
  1767. chart.getAxisMargins();
  1768. // If the plot area size has changed significantly, calculate tick
  1769. // positions again
  1770. redoHorizontal = tempWidth / chart.plotWidth > 1.1;
  1771. // Height is more sensitive, use lower threshold
  1772. redoVertical = tempHeight / chart.plotHeight > 1.05;
  1773. if (redoHorizontal || redoVertical) {
  1774. axes.forEach(function (axis) {
  1775. if (
  1776. (axis.horiz && redoHorizontal) ||
  1777. (!axis.horiz && redoVertical)
  1778. ) {
  1779. // update to reflect the new margins
  1780. axis.setTickInterval(true);
  1781. }
  1782. });
  1783. chart.getMargins(); // second pass to check for new labels
  1784. }
  1785. // Draw the borders and backgrounds
  1786. chart.drawChartBox();
  1787. // Axes
  1788. if (chart.hasCartesianSeries) {
  1789. axes.forEach(function (axis) {
  1790. if (axis.visible) {
  1791. axis.render();
  1792. }
  1793. });
  1794. }
  1795. // The series
  1796. if (!chart.seriesGroup) {
  1797. chart.seriesGroup = renderer.g('series-group')
  1798. .attr({ zIndex: 3 })
  1799. .add();
  1800. }
  1801. chart.renderSeries();
  1802. // Labels
  1803. chart.renderLabels();
  1804. // Credits
  1805. chart.addCredits();
  1806. // Handle responsiveness
  1807. if (chart.setResponsive) {
  1808. chart.setResponsive();
  1809. }
  1810. // Set flag
  1811. chart.hasRendered = true;
  1812. },
  1813. /**
  1814. * Set a new credits label for the chart.
  1815. *
  1816. * @sample highcharts/credits/credits-update/
  1817. * Add and update credits
  1818. *
  1819. * @function Highcharts.Chart#addCredits
  1820. *
  1821. * @param {Highcharts.CreditsOptions} options
  1822. * A configuration object for the new credits.
  1823. */
  1824. addCredits: function (credits) {
  1825. var chart = this;
  1826. credits = merge(true, this.options.credits, credits);
  1827. if (credits.enabled && !this.credits) {
  1828. /**
  1829. * The chart's credits label. The label has an `update` method that
  1830. * allows setting new options as per the
  1831. * [credits options set](https://api.highcharts.com/highcharts/credits).
  1832. *
  1833. * @name Highcharts.Chart#credits
  1834. * @type {Highcharts.SVGElement}
  1835. */
  1836. this.credits = this.renderer.text(
  1837. credits.text + (this.mapCredits || ''),
  1838. 0,
  1839. 0
  1840. )
  1841. .addClass('highcharts-credits')
  1842. .on('click', function () {
  1843. if (credits.href) {
  1844. win.location.href = credits.href;
  1845. }
  1846. })
  1847. .attr({
  1848. align: credits.position.align,
  1849. zIndex: 8
  1850. });
  1851. if (!chart.styledMode) {
  1852. this.credits.css(credits.style);
  1853. }
  1854. this.credits
  1855. .add()
  1856. .align(credits.position);
  1857. // Dynamically update
  1858. this.credits.update = function (options) {
  1859. chart.credits = chart.credits.destroy();
  1860. chart.addCredits(options);
  1861. };
  1862. }
  1863. },
  1864. /**
  1865. * Remove the chart and purge memory. This method is called internally
  1866. * before adding a second chart into the same container, as well as on
  1867. * window unload to prevent leaks.
  1868. *
  1869. * @sample highcharts/members/chart-destroy/
  1870. * Destroy the chart from a button
  1871. * @sample stock/members/chart-destroy/
  1872. * Destroy with Highstock
  1873. *
  1874. * @function Highcharts.Chart#destroy
  1875. *
  1876. * @fires Highcharts.Chart#event:destroy
  1877. */
  1878. destroy: function () {
  1879. var chart = this,
  1880. axes = chart.axes,
  1881. series = chart.series,
  1882. container = chart.container,
  1883. i,
  1884. parentNode = container && container.parentNode;
  1885. // fire the chart.destoy event
  1886. fireEvent(chart, 'destroy');
  1887. // Delete the chart from charts lookup array
  1888. if (chart.renderer.forExport) {
  1889. H.erase(charts, chart); // #6569
  1890. } else {
  1891. charts[chart.index] = undefined;
  1892. }
  1893. H.chartCount--;
  1894. chart.renderTo.removeAttribute('data-highcharts-chart');
  1895. // remove events
  1896. removeEvent(chart);
  1897. // ==== Destroy collections:
  1898. // Destroy axes
  1899. i = axes.length;
  1900. while (i--) {
  1901. axes[i] = axes[i].destroy();
  1902. }
  1903. // Destroy scroller & scroller series before destroying base series
  1904. if (this.scroller && this.scroller.destroy) {
  1905. this.scroller.destroy();
  1906. }
  1907. // Destroy each series
  1908. i = series.length;
  1909. while (i--) {
  1910. series[i] = series[i].destroy();
  1911. }
  1912. // ==== Destroy chart properties:
  1913. [
  1914. 'title', 'subtitle', 'chartBackground', 'plotBackground',
  1915. 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
  1916. 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
  1917. 'renderer'
  1918. ].forEach(function (name) {
  1919. var prop = chart[name];
  1920. if (prop && prop.destroy) {
  1921. chart[name] = prop.destroy();
  1922. }
  1923. });
  1924. // Remove container and all SVG, check container as it can break in IE
  1925. // when destroyed before finished loading
  1926. if (container) {
  1927. container.innerHTML = '';
  1928. removeEvent(container);
  1929. if (parentNode) {
  1930. discardElement(container);
  1931. }
  1932. }
  1933. // clean it all up
  1934. objectEach(chart, function (val, key) {
  1935. delete chart[key];
  1936. });
  1937. },
  1938. /**
  1939. * Prepare for first rendering after all data are loaded.
  1940. *
  1941. * @private
  1942. * @function Highcharts.Chart#firstRender
  1943. *
  1944. * @fires Highcharts.Chart#event:beforeRender
  1945. */
  1946. firstRender: function () {
  1947. var chart = this,
  1948. options = chart.options;
  1949. // Hook for oldIE to check whether the chart is ready to render
  1950. if (chart.isReadyToRender && !chart.isReadyToRender()) {
  1951. return;
  1952. }
  1953. // Create the container
  1954. chart.getContainer();
  1955. chart.resetMargins();
  1956. chart.setChartSize();
  1957. // Set the common chart properties (mainly invert) from the given series
  1958. chart.propFromSeries();
  1959. // get axes
  1960. chart.getAxes();
  1961. // Initialize the series
  1962. (H.isArray(options.series) ? options.series : []).forEach( // #9680
  1963. function (serieOptions) {
  1964. chart.initSeries(serieOptions);
  1965. }
  1966. );
  1967. chart.linkSeries();
  1968. // Run an event after axes and series are initialized, but before
  1969. // render. At this stage, the series data is indexed and cached in the
  1970. // xData and yData arrays, so we can access those before rendering. Used
  1971. // in Highstock.
  1972. fireEvent(chart, 'beforeRender');
  1973. // depends on inverted and on margins being set
  1974. if (Pointer) {
  1975. /**
  1976. * The Pointer that keeps track of mouse and touch interaction.
  1977. *
  1978. * @memberof Highcharts.Chart
  1979. * @name pointer
  1980. * @type {Highcharts.Pointer}
  1981. * @instance
  1982. */
  1983. chart.pointer = new Pointer(chart, options);
  1984. }
  1985. chart.render();
  1986. // Fire the load event if there are no external images
  1987. if (!chart.renderer.imgCount && chart.onload) {
  1988. chart.onload();
  1989. }
  1990. // If the chart was rendered outside the top container, put it back in
  1991. // (#3679)
  1992. chart.temporaryDisplay(true);
  1993. },
  1994. /**
  1995. * Internal function that runs on chart load, async if any images are loaded
  1996. * in the chart. Runs the callbacks and triggers the `load` and `render`
  1997. * events.
  1998. *
  1999. * @private
  2000. * @function Highcharts.Chart#onload
  2001. *
  2002. * @fires Highcharts.Chart#event:load
  2003. * @fires Highcharts.Chart#event:render
  2004. */
  2005. onload: function () {
  2006. // Run callbacks
  2007. [this.callback].concat(this.callbacks).forEach(function (fn) {
  2008. // Chart destroyed in its own callback (#3600)
  2009. if (fn && this.index !== undefined) {
  2010. fn.apply(this, [this]);
  2011. }
  2012. }, this);
  2013. fireEvent(this, 'load');
  2014. fireEvent(this, 'render');
  2015. // Set up auto resize, check for not destroyed (#6068)
  2016. if (defined(this.index)) {
  2017. this.setReflow(this.options.chart.reflow);
  2018. }
  2019. // Don't run again
  2020. this.onload = null;
  2021. }
  2022. }); // end Chart