boost.src.js 106 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Boost module
  4. *
  5. * (c) 2010-2017 Highsoft AS
  6. * Author: Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. module.exports = factory;
  14. } else {
  15. factory(Highcharts);
  16. }
  17. }(function (Highcharts) {
  18. (function (H) {
  19. /**
  20. * License: www.highcharts.com/license
  21. * Author: Christer Vasseng, Torstein Honsi
  22. *
  23. * This is a Highcharts module that draws long data series on a cannvas in order
  24. * to increase performance of the initial load time and tooltip responsiveness.
  25. *
  26. * Compatible with WebGL compatible browsers (not IE < 11).
  27. *
  28. * If this module is taken in as part of the core
  29. * - All the loading logic should be merged with core. Update styles in the
  30. * core.
  31. * - Most of the method wraps should probably be added directly in parent
  32. * methods.
  33. *
  34. * Notes for boost mode
  35. * - Area lines are not drawn
  36. * - Lines are not drawn on scatter charts
  37. * - Zones and negativeColor don't work
  38. * - Dash styles are not rendered on lines.
  39. * - Columns are always one pixel wide. Don't set the threshold too low.
  40. * - Disable animations
  41. * - Marker shapes are not supported: markers will always be circles
  42. *
  43. * Optimizing tips for users
  44. * - Set extremes (min, max) explicitly on the axes in order for Highcharts to
  45. * avoid computing extremes.
  46. * - Set enableMouseTracking to false on the series to improve total rendering
  47. * time.
  48. * - The default threshold is set based on one series. If you have multiple,
  49. * dense series, the combined number of points drawn gets higher, and you may
  50. * want to set the threshold lower in order to use optimizations.
  51. * - If drawing large scatter charts, it's beneficial to set the marker radius
  52. * to a value less than 1. This is to add additional spacing to make the chart
  53. * more readable.
  54. * - If the value increments on both the X and Y axis aren't small, consider
  55. * setting useGPUTranslations to true on the boost settings object. If you do
  56. * this and the increments are small (e.g. datetime axis with small time
  57. * increments) it may cause rendering issues due to floating point rounding
  58. * errors, so your millage may vary.
  59. *
  60. * Settings
  61. * There are two ways of setting the boost threshold:
  62. * - Per series: boost based on number of points in individual series
  63. * - Per chart: boost based on the number of series
  64. *
  65. * To set the series boost threshold, set seriesBoostThreshold on the chart
  66. * object.
  67. * To set the series-specific threshold, set boostThreshold on the series
  68. * object.
  69. *
  70. * In addition, the following can be set in the boost object:
  71. * {
  72. * //Wether or not to use alpha blending
  73. * useAlpha: boolean - default: true
  74. * //Set to true to perform translations on the GPU.
  75. * //Much faster, but may cause rendering issues
  76. * //when using values far from 0 due to floating point
  77. * //rounding issues
  78. * useGPUTranslations: boolean - default: false
  79. * //Use pre-allocated buffers, much faster,
  80. * //but may cause rendering issues with some data sets
  81. * usePreallocated: boolean - default: false
  82. * }
  83. */
  84. /**
  85. * Options for the Boost module. The Boost module allows certain series types
  86. * to be rendered by WebGL instead of the default SVG. This allows hundreds of
  87. * thousands of data points to be rendered in milliseconds. In addition to the
  88. * WebGL rendering it saves time by skipping processing and inspection of the
  89. * data wherever possible. This introduces some limitations to what features are
  90. * available in Boost mode. See [the docs](
  91. * https://www.highcharts.com/docs/advanced-chart-features/boost-module) for
  92. * details.
  93. *
  94. * In addition to the global `boost` option, each series has a
  95. * [boostThreshold](#plotOptions.series.boostThreshold) that defines when the
  96. * boost should kick in.
  97. *
  98. * Requires the `modules/boost.js` module.
  99. *
  100. * @sample {highstock} highcharts/boost/line-series-heavy-stock
  101. * Stock chart
  102. * @sample {highstock} highcharts/boost/line-series-heavy-dynamic
  103. * Dynamic stock chart
  104. * @sample highcharts/boost/line
  105. * Line chart
  106. * @sample highcharts/boost/line-series-heavy
  107. * Line chart with hundreds of series
  108. * @sample highcharts/boost/scatter
  109. * Scatter chart
  110. * @sample highcharts/boost/area
  111. * Area chart
  112. * @sample highcharts/boost/arearange
  113. * Area range chart
  114. * @sample highcharts/boost/column
  115. * Column chart
  116. * @sample highcharts/boost/columnrange
  117. * Column range chart
  118. * @sample highcharts/boost/bubble
  119. * Bubble chart
  120. * @sample highcharts/boost/heatmap
  121. * Heat map
  122. * @sample highcharts/boost/treemap
  123. * Tree map
  124. *
  125. * @product highcharts highstock
  126. * @type {Object}
  127. * @apioption boost
  128. */
  129. /**
  130. * Set the series threshold for when the boost should kick in globally.
  131. *
  132. * Setting to e.g. 20 will cause the whole chart to enter boost mode
  133. * if there are 20 or more series active. When the chart is in boost mode,
  134. * every series in it will be rendered to a common canvas. This offers
  135. * a significant speed improvment in charts with a very high
  136. * amount of series.
  137. *
  138. * @type {Number}
  139. * @default null
  140. * @apioption boost.seriesThreshold
  141. */
  142. /**
  143. * Enable or disable boost on a chart.
  144. *
  145. * @type {Boolean}
  146. * @default true
  147. * @apioption boost.enabled
  148. */
  149. /**
  150. * Debugging options for boost.
  151. * Useful for benchmarking, and general timing.
  152. *
  153. * @type {Object}
  154. * @apioption boost.debug
  155. */
  156. /**
  157. * Time the series rendering.
  158. *
  159. * This outputs the time spent on actual rendering in the console when
  160. * set to true.
  161. *
  162. * @type {Boolean}
  163. * @default false
  164. * @apioption boost.debug.timeRendering
  165. */
  166. /**
  167. * Time the series processing.
  168. *
  169. * This outputs the time spent on transforming the series data to
  170. * vertex buffers when set to true.
  171. *
  172. * @type {Boolean}
  173. * @default false
  174. * @apioption boost.debug.timeSeriesProcessing
  175. */
  176. /**
  177. * Time the the WebGL setup.
  178. *
  179. * This outputs the time spent on setting up the WebGL context,
  180. * creating shaders, and textures.
  181. *
  182. * @type {Boolean}
  183. * @default false
  184. * @apioption boost.debug.timeSetup
  185. */
  186. /**
  187. * Time the building of the k-d tree.
  188. *
  189. * This outputs the time spent building the k-d tree used for
  190. * markers etc.
  191. *
  192. * Note that the k-d tree is built async, and runs post-rendering.
  193. * Following, it does not affect the performance of the rendering itself.
  194. *
  195. * @type {Boolean}
  196. * @default false
  197. * @apioption boost.debug.timeKDTree
  198. */
  199. /**
  200. * Show the number of points skipped through culling.
  201. *
  202. * When set to true, the number of points skipped in series processing
  203. * is outputted. Points are skipped if they are closer than 1 pixel from
  204. * each other.
  205. *
  206. * @type {Boolean}
  207. * @default false
  208. * @apioption boost.debug.showSkipSummary
  209. */
  210. /**
  211. * Time the WebGL to SVG buffer copy
  212. *
  213. * After rendering, the result is copied to an image which is injected
  214. * into the SVG.
  215. *
  216. * If this property is set to true, the time it takes for the buffer copy
  217. * to complete is outputted.
  218. *
  219. * @type {Boolean}
  220. * @default false
  221. * @apioption boost.debug.timeBufferCopy
  222. */
  223. /**
  224. * Enable or disable GPU translations. GPU translations are faster than doing
  225. * the translation in JavaScript.
  226. *
  227. * This option may cause rendering issues with certain datasets.
  228. * Namely, if your dataset has large numbers with small increments (such as
  229. * timestamps), it won't work correctly. This is due to floating point
  230. * precission.
  231. *
  232. * @type {Boolean}
  233. * @default false
  234. * @apioption boost.useGPUTranslations
  235. */
  236. /**
  237. * Set the point threshold for when a series should enter boost mode.
  238. *
  239. * Setting it to e.g. 2000 will cause the series to enter boost mode when there
  240. * are 2000 or more points in the series.
  241. *
  242. * To disable boosting on the series, set the `boostThreshold` to 0. Setting it
  243. * to 1 will force boosting.
  244. *
  245. * Requires `modules/boost.js`.
  246. *
  247. * @type {Number}
  248. * @default 5000
  249. * @apioption plotOptions.series.boostThreshold
  250. */
  251. /**
  252. * If set to true, the whole chart will be boosted if one of the series
  253. * crosses its threshold, and all the series can be boosted.
  254. *
  255. * @type {Boolean}
  256. * @default true
  257. * @apioption boost.allowForce
  258. */
  259. /* global Float32Array */
  260. var win = H.win,
  261. doc = win.document,
  262. noop = function () {},
  263. Chart = H.Chart,
  264. Color = H.Color,
  265. Series = H.Series,
  266. seriesTypes = H.seriesTypes,
  267. each = H.each,
  268. extend = H.extend,
  269. addEvent = H.addEvent,
  270. fireEvent = H.fireEvent,
  271. grep = H.grep,
  272. isNumber = H.isNumber,
  273. merge = H.merge,
  274. pick = H.pick,
  275. wrap = H.wrap,
  276. plotOptions = H.getOptions().plotOptions,
  277. CHUNK_SIZE = 30000,
  278. mainCanvas = doc.createElement('canvas'),
  279. index,
  280. boostable = [
  281. 'area',
  282. 'arearange',
  283. 'column',
  284. 'columnrange',
  285. 'bar',
  286. 'line',
  287. 'scatter',
  288. 'heatmap',
  289. 'bubble',
  290. 'treemap'
  291. ],
  292. boostableMap = {};
  293. each(boostable, function (item) {
  294. boostableMap[item] = 1;
  295. });
  296. // Register color names since GL can't render those directly.
  297. Color.prototype.names = {
  298. aliceblue: '#f0f8ff',
  299. antiquewhite: '#faebd7',
  300. aqua: '#00ffff',
  301. aquamarine: '#7fffd4',
  302. azure: '#f0ffff',
  303. beige: '#f5f5dc',
  304. bisque: '#ffe4c4',
  305. black: '#000000',
  306. blanchedalmond: '#ffebcd',
  307. blue: '#0000ff',
  308. blueviolet: '#8a2be2',
  309. brown: '#a52a2a',
  310. burlywood: '#deb887',
  311. cadetblue: '#5f9ea0',
  312. chartreuse: '#7fff00',
  313. chocolate: '#d2691e',
  314. coral: '#ff7f50',
  315. cornflowerblue: '#6495ed',
  316. cornsilk: '#fff8dc',
  317. crimson: '#dc143c',
  318. cyan: '#00ffff',
  319. darkblue: '#00008b',
  320. darkcyan: '#008b8b',
  321. darkgoldenrod: '#b8860b',
  322. darkgray: '#a9a9a9',
  323. darkgreen: '#006400',
  324. darkkhaki: '#bdb76b',
  325. darkmagenta: '#8b008b',
  326. darkolivegreen: '#556b2f',
  327. darkorange: '#ff8c00',
  328. darkorchid: '#9932cc',
  329. darkred: '#8b0000',
  330. darksalmon: '#e9967a',
  331. darkseagreen: '#8fbc8f',
  332. darkslateblue: '#483d8b',
  333. darkslategray: '#2f4f4f',
  334. darkturquoise: '#00ced1',
  335. darkviolet: '#9400d3',
  336. deeppink: '#ff1493',
  337. deepskyblue: '#00bfff',
  338. dimgray: '#696969',
  339. dodgerblue: '#1e90ff',
  340. feldspar: '#d19275',
  341. firebrick: '#b22222',
  342. floralwhite: '#fffaf0',
  343. forestgreen: '#228b22',
  344. fuchsia: '#ff00ff',
  345. gainsboro: '#dcdcdc',
  346. ghostwhite: '#f8f8ff',
  347. gold: '#ffd700',
  348. goldenrod: '#daa520',
  349. gray: '#808080',
  350. green: '#008000',
  351. greenyellow: '#adff2f',
  352. honeydew: '#f0fff0',
  353. hotpink: '#ff69b4',
  354. indianred: '#cd5c5c',
  355. indigo: '#4b0082',
  356. ivory: '#fffff0',
  357. khaki: '#f0e68c',
  358. lavender: '#e6e6fa',
  359. lavenderblush: '#fff0f5',
  360. lawngreen: '#7cfc00',
  361. lemonchiffon: '#fffacd',
  362. lightblue: '#add8e6',
  363. lightcoral: '#f08080',
  364. lightcyan: '#e0ffff',
  365. lightgoldenrodyellow: '#fafad2',
  366. lightgrey: '#d3d3d3',
  367. lightgreen: '#90ee90',
  368. lightpink: '#ffb6c1',
  369. lightsalmon: '#ffa07a',
  370. lightseagreen: '#20b2aa',
  371. lightskyblue: '#87cefa',
  372. lightslateblue: '#8470ff',
  373. lightslategray: '#778899',
  374. lightsteelblue: '#b0c4de',
  375. lightyellow: '#ffffe0',
  376. lime: '#00ff00',
  377. limegreen: '#32cd32',
  378. linen: '#faf0e6',
  379. magenta: '#ff00ff',
  380. maroon: '#800000',
  381. mediumaquamarine: '#66cdaa',
  382. mediumblue: '#0000cd',
  383. mediumorchid: '#ba55d3',
  384. mediumpurple: '#9370d8',
  385. mediumseagreen: '#3cb371',
  386. mediumslateblue: '#7b68ee',
  387. mediumspringgreen: '#00fa9a',
  388. mediumturquoise: '#48d1cc',
  389. mediumvioletred: '#c71585',
  390. midnightblue: '#191970',
  391. mintcream: '#f5fffa',
  392. mistyrose: '#ffe4e1',
  393. moccasin: '#ffe4b5',
  394. navajowhite: '#ffdead',
  395. navy: '#000080',
  396. oldlace: '#fdf5e6',
  397. olive: '#808000',
  398. olivedrab: '#6b8e23',
  399. orange: '#ffa500',
  400. orangered: '#ff4500',
  401. orchid: '#da70d6',
  402. palegoldenrod: '#eee8aa',
  403. palegreen: '#98fb98',
  404. paleturquoise: '#afeeee',
  405. palevioletred: '#d87093',
  406. papayawhip: '#ffefd5',
  407. peachpuff: '#ffdab9',
  408. peru: '#cd853f',
  409. pink: '#ffc0cb',
  410. plum: '#dda0dd',
  411. powderblue: '#b0e0e6',
  412. purple: '#800080',
  413. red: '#ff0000',
  414. rosybrown: '#bc8f8f',
  415. royalblue: '#4169e1',
  416. saddlebrown: '#8b4513',
  417. salmon: '#fa8072',
  418. sandybrown: '#f4a460',
  419. seagreen: '#2e8b57',
  420. seashell: '#fff5ee',
  421. sienna: '#a0522d',
  422. silver: '#c0c0c0',
  423. skyblue: '#87ceeb',
  424. slateblue: '#6a5acd',
  425. slategray: '#708090',
  426. snow: '#fffafa',
  427. springgreen: '#00ff7f',
  428. steelblue: '#4682b4',
  429. tan: '#d2b48c',
  430. teal: '#008080',
  431. thistle: '#d8bfd8',
  432. tomato: '#ff6347',
  433. turquoise: '#40e0d0',
  434. violet: '#ee82ee',
  435. violetred: '#d02090',
  436. wheat: '#f5deb3',
  437. white: '#ffffff',
  438. whitesmoke: '#f5f5f5',
  439. yellow: '#ffff00',
  440. yellowgreen: '#9acd32'
  441. };
  442. /**
  443. * Tolerant max() funciton
  444. * @return {number} max value
  445. */
  446. function patientMax() {
  447. var args = Array.prototype.slice.call(arguments),
  448. r = -Number.MAX_VALUE;
  449. each(args, function (t) {
  450. if (
  451. typeof t !== 'undefined' &&
  452. t !== null &&
  453. typeof t.length !== 'undefined'
  454. ) {
  455. // r = r < t.length ? t.length : r;
  456. if (t.length > 0) {
  457. r = t.length;
  458. return true;
  459. }
  460. }
  461. });
  462. return r;
  463. }
  464. /*
  465. * Returns true if we should force chart series boosting
  466. * The result of this is cached in chart.boostForceChartBoost.
  467. * It's re-fetched on redraw.
  468. *
  469. * We do this because there's a lot of overhead involved when dealing
  470. * with a lot of series.
  471. *
  472. */
  473. function shouldForceChartSeriesBoosting(chart) {
  474. // If there are more than five series currently boosting,
  475. // we should boost the whole chart to avoid running out of webgl contexts.
  476. var sboostCount = 0,
  477. canBoostCount = 0,
  478. allowBoostForce = pick(
  479. chart.options.boost && chart.options.boost.allowForce,
  480. true
  481. ),
  482. series;
  483. if (typeof chart.boostForceChartBoost !== 'undefined') {
  484. return chart.boostForceChartBoost;
  485. }
  486. if (chart.series.length > 1) {
  487. for (var i = 0; i < chart.series.length; i++) {
  488. series = chart.series[i];
  489. if (boostableMap[series.type]) {
  490. ++canBoostCount;
  491. }
  492. if (patientMax(
  493. series.processedXData,
  494. series.options.data,
  495. // series.xData,
  496. series.points
  497. ) >= (series.options.boostThreshold || Number.MAX_VALUE)) {
  498. ++sboostCount;
  499. }
  500. }
  501. }
  502. chart.boostForceChartBoost =
  503. (
  504. allowBoostForce &&
  505. canBoostCount === chart.series.length &&
  506. sboostCount > 0
  507. ) ||
  508. sboostCount > 5;
  509. return chart.boostForceChartBoost;
  510. }
  511. /*
  512. * Returns true if the chart is in series boost mode
  513. * @param chart {Highchart.Chart} - the chart to check
  514. * @returns {Boolean} - true if the chart is in series boost mode
  515. */
  516. Chart.prototype.isChartSeriesBoosting = function () {
  517. var isSeriesBoosting,
  518. threshold = pick(
  519. this.options.boost && this.options.boost.seriesThreshold,
  520. 50
  521. );
  522. isSeriesBoosting = threshold <= this.series.length ||
  523. shouldForceChartSeriesBoosting(this);
  524. return isSeriesBoosting;
  525. };
  526. /*
  527. * Get the clip rectangle for a target, either a series or the chart. For the
  528. * chart, we need to consider the maximum extent of its Y axes, in case of
  529. * Highstock panes and navigator.
  530. */
  531. Chart.prototype.getBoostClipRect = function (target) {
  532. var clipBox = {
  533. x: this.plotLeft,
  534. y: this.plotTop,
  535. width: this.plotWidth,
  536. height: this.plotHeight
  537. };
  538. if (target === this) {
  539. each(this.yAxis, function (yAxis) {
  540. clipBox.y = Math.min(yAxis.pos, clipBox.y);
  541. clipBox.height = Math.max(
  542. yAxis.pos - this.plotTop + yAxis.len,
  543. clipBox.height
  544. );
  545. }, this);
  546. }
  547. return clipBox;
  548. };
  549. /*
  550. * Returns true if the series is in boost mode
  551. * @param series {Highchart.Series} - the series to check
  552. * @returns {boolean} - true if the series is in boost mode
  553. */
  554. /*
  555. function isSeriesBoosting(series, overrideThreshold) {
  556. return isChartSeriesBoosting(series.chart) ||
  557. patientMax(
  558. series.processedXData,
  559. series.options.data,
  560. series.points
  561. ) >= (
  562. overrideThreshold ||
  563. series.options.boostThreshold ||
  564. Number.MAX_VALUE
  565. );
  566. }
  567. */
  568. // START OF WEBGL ABSTRACTIONS
  569. /*
  570. * A static shader mimicing axis translation functions found in parts/Axis
  571. * @param gl {WebGLContext} - the context in which the shader is active
  572. */
  573. function GLShader(gl) {
  574. var vertShade = [
  575. /* eslint-disable */
  576. '#version 100',
  577. 'precision highp float;',
  578. 'attribute vec4 aVertexPosition;',
  579. 'attribute vec4 aColor;',
  580. 'varying highp vec2 position;',
  581. 'varying highp vec4 vColor;',
  582. 'uniform mat4 uPMatrix;',
  583. 'uniform float pSize;',
  584. 'uniform float translatedThreshold;',
  585. 'uniform bool hasThreshold;',
  586. 'uniform bool skipTranslation;',
  587. 'uniform float plotHeight;',
  588. 'uniform float xAxisTrans;',
  589. 'uniform float xAxisMin;',
  590. 'uniform float xAxisMinPad;',
  591. 'uniform float xAxisPointRange;',
  592. 'uniform float xAxisLen;',
  593. 'uniform bool xAxisPostTranslate;',
  594. 'uniform float xAxisOrdinalSlope;',
  595. 'uniform float xAxisOrdinalOffset;',
  596. 'uniform float xAxisPos;',
  597. 'uniform bool xAxisCVSCoord;',
  598. 'uniform float yAxisTrans;',
  599. 'uniform float yAxisMin;',
  600. 'uniform float yAxisMinPad;',
  601. 'uniform float yAxisPointRange;',
  602. 'uniform float yAxisLen;',
  603. 'uniform bool yAxisPostTranslate;',
  604. 'uniform float yAxisOrdinalSlope;',
  605. 'uniform float yAxisOrdinalOffset;',
  606. 'uniform float yAxisPos;',
  607. 'uniform bool yAxisCVSCoord;',
  608. 'uniform bool isBubble;',
  609. 'uniform bool bubbleSizeByArea;',
  610. 'uniform float bubbleZMin;',
  611. 'uniform float bubbleZMax;',
  612. 'uniform float bubbleZThreshold;',
  613. 'uniform float bubbleMinSize;',
  614. 'uniform float bubbleMaxSize;',
  615. 'uniform bool bubbleSizeAbs;',
  616. 'uniform bool isInverted;',
  617. 'float bubbleRadius(){',
  618. 'float value = aVertexPosition.w;',
  619. 'float zMax = bubbleZMax;',
  620. 'float zMin = bubbleZMin;',
  621. 'float radius = 0.0;',
  622. 'float pos = 0.0;',
  623. 'float zRange = zMax - zMin;',
  624. 'if (bubbleSizeAbs){',
  625. 'value = value - bubbleZThreshold;',
  626. 'zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);',
  627. 'zMin = 0.0;',
  628. '}',
  629. 'if (value < zMin){',
  630. 'radius = bubbleZMin / 2.0 - 1.0;',
  631. '} else {',
  632. 'pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;',
  633. 'if (bubbleSizeByArea && pos > 0.0){',
  634. 'pos = sqrt(pos);',
  635. '}',
  636. 'radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;',
  637. '}',
  638. 'return radius * 2.0;',
  639. '}',
  640. 'float translate(float val,',
  641. 'float pointPlacement,',
  642. 'float localA,',
  643. 'float localMin,',
  644. 'float minPixelPadding,',
  645. 'float pointRange,',
  646. 'float len,',
  647. 'bool cvsCoord',
  648. '){',
  649. 'float sign = 1.0;',
  650. 'float cvsOffset = 0.0;',
  651. 'if (cvsCoord) {',
  652. 'sign *= -1.0;',
  653. 'cvsOffset = len;',
  654. '}',
  655. 'return sign * (val - localMin) * localA + cvsOffset + ',
  656. '(sign * minPixelPadding);',//' + localA * pointPlacement * pointRange;',
  657. '}',
  658. 'float xToPixels(float value){',
  659. 'if (skipTranslation){',
  660. 'return value;// + xAxisPos;',
  661. '}',
  662. 'return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord);// + xAxisPos;',
  663. '}',
  664. 'float yToPixels(float value, float checkTreshold){',
  665. 'float v;',
  666. 'if (skipTranslation){',
  667. 'v = value;// + yAxisPos;',
  668. '} else {',
  669. 'v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord);// + yAxisPos;',
  670. 'if (v > plotHeight) {',
  671. 'v = plotHeight;',
  672. '}',
  673. '}',
  674. 'if (checkTreshold > 0.0 && hasThreshold) {',
  675. 'v = min(v, translatedThreshold);',
  676. '}',
  677. 'return v;',
  678. '}',
  679. 'void main(void) {',
  680. 'if (isBubble){',
  681. 'gl_PointSize = bubbleRadius();',
  682. '} else {',
  683. 'gl_PointSize = pSize;',
  684. '}',
  685. //'gl_PointSize = 10.0;',
  686. 'vColor = aColor;',
  687. 'if (isInverted) {',
  688. 'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.y) + yAxisPos, yToPixels(aVertexPosition.x, aVertexPosition.z) + xAxisPos, 0.0, 1.0);',
  689. '} else {',
  690. 'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);',
  691. '}',
  692. //'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
  693. '}'
  694. /* eslint-enable */
  695. ].join('\n'),
  696. // Fragment shader source
  697. fragShade = [
  698. /* eslint-disable */
  699. 'precision highp float;',
  700. 'uniform vec4 fillColor;',
  701. 'varying highp vec2 position;',
  702. 'varying highp vec4 vColor;',
  703. 'uniform sampler2D uSampler;',
  704. 'uniform bool isCircle;',
  705. 'uniform bool hasColor;',
  706. // 'vec4 toColor(float value, vec2 point) {',
  707. // 'return vec4(0.0, 0.0, 0.0, 0.0);',
  708. // '}',
  709. 'void main(void) {',
  710. 'vec4 col = fillColor;',
  711. 'vec4 tcol;',
  712. 'if (hasColor) {',
  713. 'col = vColor;',
  714. '}',
  715. 'if (isCircle) {',
  716. 'tcol = texture2D(uSampler, gl_PointCoord.st);',
  717. 'col *= tcol;',
  718. 'if (tcol.r < 0.0) {',
  719. 'discard;',
  720. '} else {',
  721. 'gl_FragColor = col;',
  722. '}',
  723. '} else {',
  724. 'gl_FragColor = col;',
  725. '}',
  726. '}'
  727. /* eslint-enable */
  728. ].join('\n'),
  729. uLocations = {},
  730. // The shader program
  731. shaderProgram,
  732. // Uniform handle to the perspective matrix
  733. pUniform,
  734. // Uniform for point size
  735. psUniform,
  736. // Uniform for fill color
  737. fillColorUniform,
  738. // Uniform for isBubble
  739. isBubbleUniform,
  740. // Uniform for bubble abs sizing
  741. bubbleSizeAbsUniform,
  742. bubbleSizeAreaUniform,
  743. // Skip translation uniform
  744. skipTranslationUniform,
  745. // Set to 1 if circle
  746. isCircleUniform,
  747. // Uniform for invertion
  748. isInverted,
  749. plotHeightUniform,
  750. // Texture uniform
  751. uSamplerUniform;
  752. /* String to shader program
  753. * @param {string} str - the program source
  754. * @param {string} type - the program type: either `vertex` or `fragment`
  755. * @returns {bool|shader}
  756. */
  757. function stringToProgram(str, type) {
  758. var t = type === 'vertex' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER,
  759. shader = gl.createShader(t)
  760. ;
  761. gl.shaderSource(shader, str);
  762. gl.compileShader(shader);
  763. if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  764. // console.error('shader error:', gl.getShaderInfoLog(shader));
  765. return false;
  766. }
  767. return shader;
  768. }
  769. /*
  770. * Create the shader.
  771. * Loads the shader program statically defined above
  772. */
  773. function createShader() {
  774. var v = stringToProgram(vertShade, 'vertex'),
  775. f = stringToProgram(fragShade, 'fragment')
  776. ;
  777. if (!v || !f) {
  778. shaderProgram = false;
  779. // console.error('error creating shader program');
  780. return false;
  781. }
  782. function uloc(n) {
  783. return gl.getUniformLocation(shaderProgram, n);
  784. }
  785. shaderProgram = gl.createProgram();
  786. gl.attachShader(shaderProgram, v);
  787. gl.attachShader(shaderProgram, f);
  788. gl.linkProgram(shaderProgram);
  789. gl.useProgram(shaderProgram);
  790. gl.bindAttribLocation(shaderProgram, 0, 'aVertexPosition');
  791. pUniform = uloc('uPMatrix');
  792. psUniform = uloc('pSize');
  793. fillColorUniform = uloc('fillColor');
  794. isBubbleUniform = uloc('isBubble');
  795. bubbleSizeAbsUniform = uloc('bubbleSizeAbs');
  796. bubbleSizeAreaUniform = uloc('bubbleSizeByArea');
  797. uSamplerUniform = uloc('uSampler');
  798. skipTranslationUniform = uloc('skipTranslation');
  799. isCircleUniform = uloc('isCircle');
  800. isInverted = uloc('isInverted');
  801. plotHeightUniform = uloc('plotHeight');
  802. return true;
  803. }
  804. /*
  805. * Destroy the shader
  806. */
  807. function destroy() {
  808. if (gl) {
  809. if (shaderProgram) {
  810. gl.deleteProgram(shaderProgram);
  811. shaderProgram = false;
  812. }
  813. }
  814. }
  815. /*
  816. * Bind the shader.
  817. * This makes the shader the active one until another one is bound,
  818. * or until 0 is bound.
  819. */
  820. function bind() {
  821. gl.useProgram(shaderProgram);
  822. }
  823. /*
  824. * Set a uniform value.
  825. * This uses a hash map to cache uniform locations.
  826. * @param name {string} - the name of the uniform to set
  827. * @param val {float} - the value to set
  828. */
  829. function setUniform(name, val) {
  830. var u = uLocations[name] = uLocations[name] ||
  831. gl.getUniformLocation(shaderProgram, name);
  832. gl.uniform1f(u, val);
  833. }
  834. /*
  835. * Set the active texture
  836. * @param texture - the texture
  837. */
  838. function setTexture() {
  839. gl.uniform1i(uSamplerUniform, 0);
  840. }
  841. /*
  842. * Set if inversion state
  843. * @flag is the state
  844. */
  845. function setInverted(flag) {
  846. gl.uniform1i(isInverted, flag);
  847. }
  848. /*
  849. * Enable/disable circle drawing
  850. */
  851. function setDrawAsCircle(flag) {
  852. gl.uniform1i(isCircleUniform, flag ? 1 : 0);
  853. }
  854. function setPlotHeight(n) {
  855. gl.uniform1f(plotHeightUniform, n);
  856. }
  857. /*
  858. * Flush
  859. */
  860. function reset() {
  861. gl.uniform1i(isBubbleUniform, 0);
  862. gl.uniform1i(isCircleUniform, 0);
  863. }
  864. /*
  865. * Set bubble uniforms
  866. * @param series {Highcharts.Series} - the series to use
  867. */
  868. function setBubbleUniforms(series, zCalcMin, zCalcMax) {
  869. var seriesOptions = series.options,
  870. zMin = Number.MAX_VALUE,
  871. zMax = -Number.MAX_VALUE;
  872. if (series.type === 'bubble') {
  873. zMin = pick(seriesOptions.zMin, Math.min(
  874. zMin,
  875. Math.max(
  876. zCalcMin,
  877. seriesOptions.displayNegative === false ?
  878. seriesOptions.zThreshold : -Number.MAX_VALUE
  879. )
  880. ));
  881. zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
  882. gl.uniform1i(isBubbleUniform, 1);
  883. gl.uniform1i(isCircleUniform, 1);
  884. gl.uniform1i(
  885. bubbleSizeAreaUniform,
  886. series.options.sizeBy !== 'width'
  887. );
  888. gl.uniform1i(
  889. bubbleSizeAbsUniform,
  890. series.options.sizeByAbsoluteValue
  891. );
  892. setUniform('bubbleZMin', zMin);
  893. setUniform('bubbleZMax', zMax);
  894. setUniform('bubbleZThreshold', series.options.zThreshold);
  895. setUniform('bubbleMinSize', series.minPxSize);
  896. setUniform('bubbleMaxSize', series.maxPxSize);
  897. }
  898. }
  899. /*
  900. * Set the Color uniform.
  901. * @param color {Array<float>} - an array with RGBA values
  902. */
  903. function setColor(color) {
  904. gl.uniform4f(
  905. fillColorUniform,
  906. color[0] / 255.0,
  907. color[1] / 255.0,
  908. color[2] / 255.0,
  909. color[3]
  910. );
  911. }
  912. /*
  913. * Set skip translation
  914. */
  915. function setSkipTranslation(flag) {
  916. gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
  917. }
  918. /*
  919. * Set the perspective matrix
  920. * @param m {Matrix4x4} - the matrix
  921. */
  922. function setPMatrix(m) {
  923. gl.uniformMatrix4fv(pUniform, false, m);
  924. }
  925. /*
  926. * Set the point size.
  927. * @param p {float} - point size
  928. */
  929. function setPointSize(p) {
  930. gl.uniform1f(psUniform, p);
  931. }
  932. /*
  933. * Get the shader program handle
  934. * @returns {GLInt} - the handle for the program
  935. */
  936. function getProgram() {
  937. return shaderProgram;
  938. }
  939. if (gl) {
  940. createShader();
  941. }
  942. return {
  943. psUniform: function () {
  944. return psUniform;
  945. },
  946. pUniform: function () {
  947. return pUniform;
  948. },
  949. fillColorUniform: function () {
  950. return fillColorUniform;
  951. },
  952. setPlotHeight: setPlotHeight,
  953. setBubbleUniforms: setBubbleUniforms,
  954. bind: bind,
  955. program: getProgram,
  956. create: createShader,
  957. setUniform: setUniform,
  958. setPMatrix: setPMatrix,
  959. setColor: setColor,
  960. setPointSize: setPointSize,
  961. setSkipTranslation: setSkipTranslation,
  962. setTexture: setTexture,
  963. setDrawAsCircle: setDrawAsCircle,
  964. reset: reset,
  965. setInverted: setInverted,
  966. destroy: destroy
  967. };
  968. }
  969. /*
  970. * Vertex Buffer abstraction
  971. * A vertex buffer is a set of vertices which are passed to the GPU
  972. * in a single call.
  973. * @param gl {WebGLContext} - the context in which to create the buffer
  974. * @param shader {GLShader} - the shader to use
  975. */
  976. function GLVertexBuffer(gl, shader, dataComponents /* , type */) {
  977. var buffer = false,
  978. vertAttribute = false,
  979. components = dataComponents || 2,
  980. preAllocated = false,
  981. iterator = 0,
  982. // farray = false,
  983. data;
  984. // type = type || 'float';
  985. function destroy() {
  986. if (buffer) {
  987. gl.deleteBuffer(buffer);
  988. buffer = false;
  989. vertAttribute = false;
  990. }
  991. iterator = 0;
  992. components = dataComponents || 2;
  993. data = [];
  994. }
  995. /*
  996. * Build the buffer
  997. * @param dataIn {Array<float>} - a 0 padded array of indices
  998. * @param attrib {String} - the name of the Attribute to bind the buffer to
  999. * @param dataComponents {Integer} - the number of components per. indice
  1000. */
  1001. function build(dataIn, attrib, dataComponents) {
  1002. var farray;
  1003. data = dataIn || [];
  1004. if ((!data || data.length === 0) && !preAllocated) {
  1005. // console.error('trying to render empty vbuffer');
  1006. destroy();
  1007. return false;
  1008. }
  1009. components = dataComponents || components;
  1010. if (buffer) {
  1011. gl.deleteBuffer(buffer);
  1012. }
  1013. if (!preAllocated) {
  1014. farray = new Float32Array(data);
  1015. }
  1016. buffer = gl.createBuffer();
  1017. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  1018. gl.bufferData(
  1019. gl.ARRAY_BUFFER,
  1020. preAllocated || farray,
  1021. gl.STATIC_DRAW
  1022. );
  1023. // gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
  1024. vertAttribute = gl.getAttribLocation(shader.program(), attrib);
  1025. gl.enableVertexAttribArray(vertAttribute);
  1026. // Trigger cleanup
  1027. farray = false;
  1028. return true;
  1029. }
  1030. /*
  1031. * Bind the buffer
  1032. */
  1033. function bind() {
  1034. if (!buffer) {
  1035. return false;
  1036. }
  1037. // gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
  1038. // gl.enableVertexAttribArray(vertAttribute);
  1039. // gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  1040. gl.vertexAttribPointer(
  1041. vertAttribute, components, gl.FLOAT, false, 0, 0
  1042. );
  1043. // gl.enableVertexAttribArray(vertAttribute);
  1044. }
  1045. /*
  1046. * Render the buffer
  1047. * @param from {Integer} - the start indice
  1048. * @param to {Integer} - the end indice
  1049. * @param drawMode {String} - the draw mode
  1050. */
  1051. function render(from, to, drawMode) {
  1052. var length = preAllocated ? preAllocated.length : data.length;
  1053. if (!buffer) {
  1054. return false;
  1055. }
  1056. if (!length) {
  1057. return false;
  1058. }
  1059. if (!from || from > length || from < 0) {
  1060. from = 0;
  1061. }
  1062. if (!to || to > length) {
  1063. to = length;
  1064. }
  1065. drawMode = drawMode || 'points';
  1066. gl.drawArrays(
  1067. gl[drawMode.toUpperCase()],
  1068. from / components,
  1069. (to - from) / components
  1070. );
  1071. return true;
  1072. }
  1073. function push(x, y, a, b) {
  1074. if (preAllocated) { // && iterator <= preAllocated.length - 4) {
  1075. preAllocated[++iterator] = x;
  1076. preAllocated[++iterator] = y;
  1077. preAllocated[++iterator] = a;
  1078. preAllocated[++iterator] = b;
  1079. }
  1080. }
  1081. /*
  1082. * Note about pre-allocated buffers:
  1083. * - This is slower for charts with many series
  1084. */
  1085. function allocate(size) {
  1086. size *= 4;
  1087. iterator = -1;
  1088. preAllocated = new Float32Array(size);
  1089. }
  1090. // /////////////////////////////////////////////////////////////////////////
  1091. return {
  1092. destroy: destroy,
  1093. bind: bind,
  1094. data: data,
  1095. build: build,
  1096. render: render,
  1097. allocate: allocate,
  1098. push: push
  1099. };
  1100. }
  1101. /* Main renderer. Used to render series.
  1102. * Notes to self:
  1103. * - May be able to build a point map by rendering to a separate canvas
  1104. * and encoding values in the color data.
  1105. * - Need to figure out a way to transform the data quicker
  1106. */
  1107. function GLRenderer(postRenderCallback) {
  1108. var // Shader
  1109. shader = false,
  1110. // Vertex buffers - keyed on shader attribute name
  1111. vbuffer = false,
  1112. // Opengl context
  1113. gl = false,
  1114. // Width of our viewport in pixels
  1115. width = 0,
  1116. // Height of our viewport in pixels
  1117. height = 0,
  1118. // The data to render - array of coordinates
  1119. data = false,
  1120. // The marker data
  1121. markerData = false,
  1122. // Is the texture ready?
  1123. textureIsReady = false,
  1124. // Exports
  1125. exports = {},
  1126. // Is it inited?
  1127. isInited = false,
  1128. // The series stack
  1129. series = [],
  1130. // Texture for circles
  1131. circleTexture = doc.createElement('canvas'),
  1132. // Context for circle texture
  1133. circleCtx = circleTexture.getContext('2d'),
  1134. // Handle for the circle texture
  1135. circleTextureHandle,
  1136. // Things to draw as "rectangles" (i.e lines)
  1137. asBar = {
  1138. 'column': true,
  1139. 'columnrange': true,
  1140. 'bar': true,
  1141. 'area': true,
  1142. 'arearange': true
  1143. },
  1144. asCircle = {
  1145. 'scatter': true,
  1146. 'bubble': true
  1147. },
  1148. // Render settings
  1149. settings = {
  1150. pointSize: 1,
  1151. lineWidth: 1,
  1152. fillColor: '#AA00AA',
  1153. useAlpha: true,
  1154. usePreallocated: false,
  1155. useGPUTranslations: false,
  1156. debug: {
  1157. timeRendering: false,
  1158. timeSeriesProcessing: false,
  1159. timeSetup: false,
  1160. timeBufferCopy: false,
  1161. timeKDTree: false,
  1162. showSkipSummary: false
  1163. }
  1164. };
  1165. // /////////////////////////////////////////////////////////////////////////
  1166. function setOptions(options) {
  1167. merge(true, settings, options);
  1168. }
  1169. function seriesPointCount(series) {
  1170. var isStacked,
  1171. xData,
  1172. s;
  1173. if (series.isSeriesBoosting) {
  1174. isStacked = !!series.options.stacking;
  1175. xData = (
  1176. series.xData ||
  1177. series.options.xData ||
  1178. series.processedXData
  1179. );
  1180. s = (isStacked ? series.data : (xData || series.options.data))
  1181. .length;
  1182. if (series.type === 'treemap') {
  1183. s *= 12;
  1184. } else if (series.type === 'heatmap') {
  1185. s *= 6;
  1186. } else if (asBar[series.type]) {
  1187. s *= 2;
  1188. }
  1189. return s;
  1190. }
  1191. return 0;
  1192. }
  1193. /* Allocate a float buffer to fit all series */
  1194. function allocateBuffer(chart) {
  1195. var s = 0;
  1196. if (!settings.usePreallocated) {
  1197. return;
  1198. }
  1199. each(chart.series, function (series) {
  1200. if (series.isSeriesBoosting) {
  1201. s += seriesPointCount(series);
  1202. }
  1203. });
  1204. vbuffer.allocate(s);
  1205. }
  1206. function allocateBufferForSingleSeries(series) {
  1207. var s = 0;
  1208. if (!settings.usePreallocated) {
  1209. return;
  1210. }
  1211. if (series.isSeriesBoosting) {
  1212. s = seriesPointCount(series);
  1213. }
  1214. vbuffer.allocate(s);
  1215. }
  1216. /*
  1217. * Returns an orthographic perspective matrix
  1218. * @param {number} width - the width of the viewport in pixels
  1219. * @param {number} height - the height of the viewport in pixels
  1220. */
  1221. function orthoMatrix(width, height) {
  1222. var near = 0,
  1223. far = 1;
  1224. return [
  1225. 2 / width, 0, 0, 0,
  1226. 0, -(2 / height), 0, 0,
  1227. 0, 0, -2 / (far - near), 0,
  1228. -1, 1, -(far + near) / (far - near), 1
  1229. ];
  1230. }
  1231. /*
  1232. * Clear the depth and color buffer
  1233. */
  1234. function clear() {
  1235. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  1236. }
  1237. /*
  1238. * Get the WebGL context
  1239. * @returns {WebGLContext} - the context
  1240. */
  1241. function getGL() {
  1242. return gl;
  1243. }
  1244. /*
  1245. * Push data for a single series
  1246. * This calculates additional vertices and transforms the data to be
  1247. * aligned correctly in memory
  1248. */
  1249. function pushSeriesData(series, inst) {
  1250. var isRange = series.pointArrayMap &&
  1251. series.pointArrayMap.join(',') === 'low,high',
  1252. chart = series.chart,
  1253. options = series.options,
  1254. isStacked = !!options.stacking,
  1255. rawData = options.data,
  1256. xExtremes = series.xAxis.getExtremes(),
  1257. xMin = xExtremes.min,
  1258. xMax = xExtremes.max,
  1259. yExtremes = series.yAxis.getExtremes(),
  1260. yMin = yExtremes.min,
  1261. yMax = yExtremes.max,
  1262. xData = series.xData || options.xData || series.processedXData,
  1263. yData = series.yData || options.yData || series.processedYData,
  1264. zData = series.zData || options.zData || series.processedZData,
  1265. yAxis = series.yAxis,
  1266. xAxis = series.xAxis,
  1267. plotHeight = series.chart.plotHeight,
  1268. plotWidth = series.chart.plotWidth,
  1269. useRaw = !xData || xData.length === 0,
  1270. // threshold = options.threshold,
  1271. // yBottom = chart.yAxis[0].getThreshold(threshold),
  1272. // hasThreshold = isNumber(threshold),
  1273. // colorByPoint = series.options.colorByPoint,
  1274. // This is required for color by point, so make sure this is
  1275. // uncommented if enabling that
  1276. // colorIndex = 0,
  1277. // Required for color axis support
  1278. // caxis,
  1279. connectNulls = options.connectNulls,
  1280. // For some reason eslint doesn't pick up that this is actually used
  1281. maxVal, // eslint-disable-line no-unused-vars
  1282. points = series.points || false,
  1283. lastX = false,
  1284. lastY = false,
  1285. minVal,
  1286. color,
  1287. scolor,
  1288. sdata = isStacked ? series.data : (xData || rawData),
  1289. closestLeft = { x: -Number.MAX_VALUE, y: 0 },
  1290. closestRight = { x: Number.MIN_VALUE, y: 0 },
  1291. skipped = 0,
  1292. cullXThreshold = 1,
  1293. cullYThreshold = 1,
  1294. // The following are used in the builder while loop
  1295. x,
  1296. y,
  1297. d,
  1298. z,
  1299. i = -1,
  1300. px = false,
  1301. nx = false,
  1302. // This is in fact used.
  1303. low, // eslint-disable-line no-unused-vars
  1304. chartDestroyed = typeof chart.index === 'undefined',
  1305. nextInside = false,
  1306. prevInside = false,
  1307. pcolor = false,
  1308. drawAsBar = asBar[series.type],
  1309. isXInside = false,
  1310. isYInside = true;
  1311. if (options.boostData && options.boostData.length > 0) {
  1312. return;
  1313. }
  1314. if (chart.inverted) {
  1315. plotHeight = series.chart.plotWidth;
  1316. plotWidth = series.chart.plotHeight;
  1317. }
  1318. series.closestPointRangePx = Number.MAX_VALUE;
  1319. // Push color to color buffer - need to do this per. vertex
  1320. function pushColor(color) {
  1321. if (color) {
  1322. inst.colorData.push(color[0]);
  1323. inst.colorData.push(color[1]);
  1324. inst.colorData.push(color[2]);
  1325. inst.colorData.push(color[3]);
  1326. }
  1327. }
  1328. // Push a vertice to the data buffer
  1329. function vertice(x, y, checkTreshold, pointSize, color) {
  1330. pushColor(color);
  1331. if (settings.usePreallocated) {
  1332. vbuffer.push(x, y, checkTreshold ? 1 : 0, pointSize || 1);
  1333. } else {
  1334. data.push(x);
  1335. data.push(y);
  1336. data.push(checkTreshold ? 1 : 0);
  1337. data.push(pointSize || 1);
  1338. }
  1339. }
  1340. function closeSegment() {
  1341. if (inst.segments.length) {
  1342. inst.segments[inst.segments.length - 1].to = data.length;
  1343. }
  1344. }
  1345. // Create a new segment for the current set
  1346. function beginSegment() {
  1347. // Insert a segment on the series.
  1348. // A segment is just a start indice.
  1349. // When adding a segment, if one exists from before, it should
  1350. // set the previous segment's end
  1351. if (inst.segments.length &&
  1352. inst.segments[inst.segments.length - 1].from === data.length
  1353. ) {
  1354. return;
  1355. }
  1356. closeSegment();
  1357. inst.segments.push({
  1358. from: data.length
  1359. });
  1360. }
  1361. // Push a rectangle to the data buffer
  1362. function pushRect(x, y, w, h, color) {
  1363. pushColor(color);
  1364. vertice(x + w, y);
  1365. pushColor(color);
  1366. vertice(x, y);
  1367. pushColor(color);
  1368. vertice(x, y + h);
  1369. pushColor(color);
  1370. vertice(x, y + h);
  1371. pushColor(color);
  1372. vertice(x + w, y + h);
  1373. pushColor(color);
  1374. vertice(x + w, y);
  1375. }
  1376. // Create the first segment
  1377. beginSegment();
  1378. // Special case for point shapes
  1379. if (points && points.length > 0) {
  1380. // If we're doing points, we assume that the points are already
  1381. // translated, so we skip the shader translation.
  1382. inst.skipTranslation = true;
  1383. // Force triangle draw mode
  1384. inst.drawMode = 'triangles';
  1385. // We don't have a z component in the shader, so we need to sort.
  1386. if (points[0].node && points[0].node.levelDynamic) {
  1387. points.sort(function (a, b) {
  1388. if (a.node) {
  1389. if (a.node.levelDynamic > b.node.levelDynamic) {
  1390. return 1;
  1391. } else if (a.node.levelDynamic < b.node.levelDynamic) {
  1392. return -1;
  1393. }
  1394. }
  1395. return 0;
  1396. });
  1397. }
  1398. each(points, function (point) {
  1399. var plotY = point.plotY,
  1400. shapeArgs,
  1401. swidth,
  1402. pointAttr;
  1403. if (
  1404. typeof plotY !== 'undefined' &&
  1405. !isNaN(plotY) &&
  1406. point.y !== null
  1407. ) {
  1408. shapeArgs = point.shapeArgs;
  1409. pointAttr = point.series.pointAttribs(point);
  1410. swidth = pointAttr['stroke-width'] || 0;
  1411. // Handle point colors
  1412. color = H.color(pointAttr.fill).rgba;
  1413. color[0] /= 255.0;
  1414. color[1] /= 255.0;
  1415. color[2] /= 255.0;
  1416. // So there are two ways of doing this. Either we can
  1417. // create a rectangle of two triangles, or we can do a
  1418. // point and use point size. Latter is faster, but
  1419. // only supports squares. So we're doing triangles.
  1420. // We could also use one color per. vertice to get
  1421. // better color interpolation.
  1422. // If there's stroking, we do an additional rect
  1423. if (series.type === 'treemap') {
  1424. swidth = swidth || 1;
  1425. scolor = H.color(pointAttr.stroke).rgba;
  1426. scolor[0] /= 255.0;
  1427. scolor[1] /= 255.0;
  1428. scolor[2] /= 255.0;
  1429. pushRect(
  1430. shapeArgs.x,
  1431. shapeArgs.y,
  1432. shapeArgs.width,
  1433. shapeArgs.height,
  1434. scolor
  1435. );
  1436. swidth /= 2;
  1437. }
  1438. // } else {
  1439. // swidth = 0;
  1440. // }
  1441. // Fixes issues with inverted heatmaps (see #6981)
  1442. // The root cause is that the coordinate system is flipped.
  1443. // In other words, instead of [0,0] being top-left, it's
  1444. // bottom-right. This causes a vertical and horizontal flip
  1445. // in the resulting image, making it rotated 180 degrees.
  1446. if (series.type === 'heatmap' && chart.inverted) {
  1447. shapeArgs.x = xAxis.len - shapeArgs.x;
  1448. shapeArgs.y = yAxis.len - shapeArgs.y;
  1449. shapeArgs.width = -shapeArgs.width;
  1450. shapeArgs.height = -shapeArgs.height;
  1451. }
  1452. pushRect(
  1453. shapeArgs.x + swidth,
  1454. shapeArgs.y + swidth,
  1455. shapeArgs.width - (swidth * 2),
  1456. shapeArgs.height - (swidth * 2),
  1457. color
  1458. );
  1459. }
  1460. });
  1461. closeSegment();
  1462. return;
  1463. }
  1464. // Extract color axis
  1465. // each(chart.axes || [], function (a) {
  1466. // if (H.ColorAxis && a instanceof H.ColorAxis) {
  1467. // caxis = a;
  1468. // }
  1469. // });
  1470. while (i < sdata.length - 1) {
  1471. d = sdata[++i];
  1472. // px = x = y = z = nx = low = false;
  1473. // chartDestroyed = typeof chart.index === 'undefined';
  1474. // nextInside = prevInside = pcolor = isXInside = isYInside = false;
  1475. // drawAsBar = asBar[series.type];
  1476. if (chartDestroyed) {
  1477. break;
  1478. }
  1479. // Uncomment this to enable color by point.
  1480. // This currently left disabled as the charts look really ugly
  1481. // when enabled and there's a lot of points.
  1482. // Leaving in for the future (tm).
  1483. // if (colorByPoint) {
  1484. // colorIndex = ++colorIndex %
  1485. // series.chart.options.colors.length;
  1486. // pcolor = toRGBAFast(series.chart.options.colors[colorIndex]);
  1487. // pcolor[0] /= 255.0;
  1488. // pcolor[1] /= 255.0;
  1489. // pcolor[2] /= 255.0;
  1490. // }
  1491. if (useRaw) {
  1492. x = d[0];
  1493. y = d[1];
  1494. if (sdata[i + 1]) {
  1495. nx = sdata[i + 1][0];
  1496. }
  1497. if (sdata[i - 1]) {
  1498. px = sdata[i - 1][0];
  1499. }
  1500. if (d.length >= 3) {
  1501. z = d[2];
  1502. if (d[2] > inst.zMax) {
  1503. inst.zMax = d[2];
  1504. }
  1505. if (d[2] < inst.zMin) {
  1506. inst.zMin = d[2];
  1507. }
  1508. }
  1509. } else {
  1510. x = d;
  1511. y = yData[i];
  1512. if (sdata[i + 1]) {
  1513. nx = sdata[i + 1];
  1514. }
  1515. if (sdata[i - 1]) {
  1516. px = sdata[i - 1];
  1517. }
  1518. if (zData && zData.length) {
  1519. z = zData[i];
  1520. if (zData[i] > inst.zMax) {
  1521. inst.zMax = zData[i];
  1522. }
  1523. if (zData[i] < inst.zMin) {
  1524. inst.zMin = zData[i];
  1525. }
  1526. }
  1527. }
  1528. if (!connectNulls && (x === null || y === null)) {
  1529. beginSegment();
  1530. continue;
  1531. }
  1532. if (nx && nx >= xMin && nx <= xMax) {
  1533. nextInside = true;
  1534. }
  1535. if (px && px >= xMin && px <= xMax) {
  1536. prevInside = true;
  1537. }
  1538. if (isRange) {
  1539. if (useRaw) {
  1540. y = d.slice(1, 3);
  1541. }
  1542. low = y[0];
  1543. y = y[1];
  1544. } else if (isStacked) {
  1545. x = d.x;
  1546. y = d.stackY;
  1547. low = y - d.y;
  1548. }
  1549. if (yMin !== null &&
  1550. typeof yMin !== 'undefined' &&
  1551. yMax !== null &&
  1552. typeof yMax !== 'undefined'
  1553. ) {
  1554. isYInside = y >= yMin && y <= yMax;
  1555. }
  1556. if (x > xMax && closestRight.x < xMax) {
  1557. closestRight.x = x;
  1558. closestRight.y = y;
  1559. }
  1560. if (x < xMin && closestLeft.x < xMin) {
  1561. closestLeft.x = x;
  1562. closestLeft.y = y;
  1563. }
  1564. if (y === null && connectNulls) {
  1565. continue;
  1566. }
  1567. // Cull points outside the extremes
  1568. if (y === null || !isYInside) {
  1569. beginSegment();
  1570. continue;
  1571. }
  1572. if (x >= xMin && x <= xMax) {
  1573. isXInside = true;
  1574. }
  1575. if (!isXInside && !nextInside && !prevInside) {
  1576. continue;
  1577. }
  1578. // Skip translations - temporary floating point fix
  1579. if (!settings.useGPUTranslations) {
  1580. inst.skipTranslation = true;
  1581. x = xAxis.toPixels(x, true);
  1582. y = yAxis.toPixels(y, true);
  1583. // Make sure we're not drawing outside of the chart area.
  1584. // See #6594.
  1585. if (y > plotHeight) {
  1586. y = plotHeight;
  1587. }
  1588. if (x > plotWidth) {
  1589. x = plotWidth;
  1590. }
  1591. }
  1592. if (drawAsBar) {
  1593. maxVal = y;
  1594. minVal = low;
  1595. if (low === false || typeof low === 'undefined') {
  1596. if (y < 0) {
  1597. minVal = y;
  1598. } else {
  1599. minVal = 0;
  1600. }
  1601. }
  1602. if (!settings.useGPUTranslations) {
  1603. minVal = yAxis.toPixels(minVal, true);
  1604. }
  1605. // Need to add an extra point here
  1606. vertice(x, minVal, 0, 0, pcolor);
  1607. }
  1608. // No markers on out of bounds things.
  1609. // Out of bound things are shown if and only if the next
  1610. // or previous point is inside the rect.
  1611. if (inst.hasMarkers) { // && isXInside) {
  1612. // x = H.correctFloat(
  1613. // Math.min(Math.max(-1e5, xAxis.translate(
  1614. // x,
  1615. // 0,
  1616. // 0,
  1617. // 0,
  1618. // 1,
  1619. // 0.5,
  1620. // false
  1621. // )), 1e5)
  1622. // );
  1623. if (lastX !== false) {
  1624. series.closestPointRangePx = Math.min(
  1625. series.closestPointRangePx,
  1626. Math.abs(x - lastX)
  1627. );
  1628. }
  1629. }
  1630. // If the last _drawn_ point is closer to this point than the
  1631. // threshold, skip it. Shaves off 20-100ms in processing.
  1632. if (!settings.useGPUTranslations &&
  1633. !settings.usePreallocated &&
  1634. (lastX && x - lastX < cullXThreshold) &&
  1635. (lastY && Math.abs(y - lastY) < cullYThreshold)
  1636. ) {
  1637. if (settings.debug.showSkipSummary) {
  1638. ++skipped;
  1639. }
  1640. continue;
  1641. }
  1642. // Do step line if enabled.
  1643. // Draws an additional point at the old Y at the new X.
  1644. // See #6976.
  1645. if (options.step) {
  1646. vertice(
  1647. x,
  1648. lastY,
  1649. 0,
  1650. 2,
  1651. pcolor
  1652. );
  1653. }
  1654. vertice(
  1655. x,
  1656. y,
  1657. 0,
  1658. series.type === 'bubble' ? (z || 1) : 2,
  1659. pcolor
  1660. );
  1661. // Uncomment this to support color axis.
  1662. // if (caxis) {
  1663. // color = H.color(caxis.toColor(y)).rgba;
  1664. // inst.colorData.push(color[0] / 255.0);
  1665. // inst.colorData.push(color[1] / 255.0);
  1666. // inst.colorData.push(color[2] / 255.0);
  1667. // inst.colorData.push(color[3]);
  1668. // }
  1669. lastX = x;
  1670. lastY = y;
  1671. }
  1672. if (settings.debug.showSkipSummary) {
  1673. console.log('skipped points:', skipped); // eslint-disable-line no-console
  1674. }
  1675. function pushSupplementPoint(point) {
  1676. if (!settings.useGPUTranslations) {
  1677. inst.skipTranslation = true;
  1678. point.x = xAxis.toPixels(point.x, true);
  1679. point.y = yAxis.toPixels(point.y, true);
  1680. }
  1681. // We should only do this for lines, and we should ignore markers
  1682. // since there's no point here that would have a marker.
  1683. vertice(
  1684. point.x,
  1685. point.y,
  1686. 0,
  1687. 2
  1688. );
  1689. }
  1690. if (!lastX &&
  1691. connectNulls !== false &&
  1692. closestLeft > -Number.MAX_VALUE &&
  1693. closestRight < Number.MAX_VALUE) {
  1694. // There are no points within the selected range
  1695. pushSupplementPoint(closestLeft);
  1696. pushSupplementPoint(closestRight);
  1697. }
  1698. closeSegment();
  1699. }
  1700. /*
  1701. * Push a series to the renderer
  1702. * If we render the series immediatly, we don't have to loop later
  1703. * @param s {Highchart.Series} - the series to push
  1704. */
  1705. function pushSeries(s) {
  1706. if (series.length > 0) {
  1707. // series[series.length - 1].to = data.length;
  1708. if (series[series.length - 1].hasMarkers) {
  1709. series[series.length - 1].markerTo = markerData.length;
  1710. }
  1711. }
  1712. if (settings.debug.timeSeriesProcessing) {
  1713. console.time('building ' + s.type + ' series'); // eslint-disable-line no-console
  1714. }
  1715. series.push({
  1716. segments: [],
  1717. // from: data.length,
  1718. markerFrom: markerData.length,
  1719. // Push RGBA values to this array to use per. point coloring.
  1720. // It should be 0-padded, so each component should be pushed in
  1721. // succession.
  1722. colorData: [],
  1723. series: s,
  1724. zMin: Number.MAX_VALUE,
  1725. zMax: -Number.MAX_VALUE,
  1726. hasMarkers: s.options.marker ?
  1727. s.options.marker.enabled !== false :
  1728. false,
  1729. showMarksers: true,
  1730. drawMode: ({
  1731. 'area': 'lines',
  1732. 'arearange': 'lines',
  1733. 'areaspline': 'line_strip',
  1734. 'column': 'lines',
  1735. 'columnrange': 'lines',
  1736. 'bar': 'lines',
  1737. 'line': 'line_strip',
  1738. 'scatter': 'points',
  1739. 'heatmap': 'triangles',
  1740. 'treemap': 'triangles',
  1741. 'bubble': 'points'
  1742. })[s.type] || 'line_strip'
  1743. });
  1744. // Add the series data to our buffer(s)
  1745. pushSeriesData(s, series[series.length - 1]);
  1746. if (settings.debug.timeSeriesProcessing) {
  1747. console.timeEnd('building ' + s.type + ' series'); // eslint-disable-line no-console
  1748. }
  1749. }
  1750. /*
  1751. * Flush the renderer.
  1752. * This removes pushed series and vertices.
  1753. * Should be called after clearing and before rendering
  1754. */
  1755. function flush() {
  1756. series = [];
  1757. exports.data = data = [];
  1758. markerData = [];
  1759. if (vbuffer) {
  1760. vbuffer.destroy();
  1761. }
  1762. }
  1763. /*
  1764. * Pass x-axis to shader
  1765. * @param axis {Highcharts.Axis} - the x-axis
  1766. */
  1767. function setXAxis(axis) {
  1768. if (!shader) {
  1769. return;
  1770. }
  1771. shader.setUniform('xAxisTrans', axis.transA);
  1772. shader.setUniform('xAxisMin', axis.min);
  1773. shader.setUniform('xAxisMinPad', axis.minPixelPadding);
  1774. shader.setUniform('xAxisPointRange', axis.pointRange);
  1775. shader.setUniform('xAxisLen', axis.len);
  1776. shader.setUniform('xAxisPos', axis.pos);
  1777. shader.setUniform('xAxisCVSCoord', !axis.horiz);
  1778. }
  1779. /*
  1780. * Pass y-axis to shader
  1781. * @param axis {Highcharts.Axis} - the y-axis
  1782. */
  1783. function setYAxis(axis) {
  1784. if (!shader) {
  1785. return;
  1786. }
  1787. shader.setUniform('yAxisTrans', axis.transA);
  1788. shader.setUniform('yAxisMin', axis.min);
  1789. shader.setUniform('yAxisMinPad', axis.minPixelPadding);
  1790. shader.setUniform('yAxisPointRange', axis.pointRange);
  1791. shader.setUniform('yAxisLen', axis.len);
  1792. shader.setUniform('yAxisPos', axis.pos);
  1793. shader.setUniform('yAxisCVSCoord', !axis.horiz);
  1794. }
  1795. /*
  1796. * Set the translation threshold
  1797. * @param has {boolean} - has threshold flag
  1798. * @param translation {Float} - the threshold
  1799. */
  1800. function setThreshold(has, translation) {
  1801. shader.setUniform('hasThreshold', has);
  1802. shader.setUniform('translatedThreshold', translation);
  1803. }
  1804. /*
  1805. * Render the data
  1806. * This renders all pushed series.
  1807. */
  1808. function render(chart) {
  1809. if (chart) {
  1810. if (!chart.chartHeight || !chart.chartWidth) {
  1811. // chart.setChartSize();
  1812. }
  1813. width = chart.chartWidth || 800;
  1814. height = chart.chartHeight || 400;
  1815. } else {
  1816. return false;
  1817. }
  1818. if (!gl || !width || !height) {
  1819. return false;
  1820. }
  1821. if (settings.debug.timeRendering) {
  1822. console.time('gl rendering'); // eslint-disable-line no-console
  1823. }
  1824. gl.canvas.width = width;
  1825. gl.canvas.height = height;
  1826. shader.bind();
  1827. gl.viewport(0, 0, width, height);
  1828. shader.setPMatrix(orthoMatrix(width, height));
  1829. shader.setPlotHeight(chart.plotHeight);
  1830. if (settings.lineWidth > 1 && !H.isMS) {
  1831. gl.lineWidth(settings.lineWidth);
  1832. }
  1833. vbuffer.build(exports.data, 'aVertexPosition', 4);
  1834. vbuffer.bind();
  1835. if (textureIsReady) {
  1836. gl.bindTexture(gl.TEXTURE_2D, circleTextureHandle);
  1837. shader.setTexture(circleTextureHandle);
  1838. }
  1839. shader.setInverted(chart.inverted);
  1840. // Render the series
  1841. each(series, function (s, si) {
  1842. var options = s.series.options,
  1843. sindex,
  1844. lineWidth = typeof options.lineWidth !== 'undefined' ?
  1845. options.lineWidth :
  1846. 1,
  1847. threshold = options.threshold,
  1848. hasThreshold = isNumber(threshold),
  1849. yBottom = s.series.yAxis.getThreshold(threshold),
  1850. translatedThreshold = yBottom,
  1851. cbuffer,
  1852. showMarkers = pick(
  1853. options.marker ? options.marker.enabled : null,
  1854. s.series.xAxis.isRadial ? true : null,
  1855. s.series.closestPointRangePx >
  1856. 2 * ((
  1857. options.marker ?
  1858. options.marker.radius :
  1859. 10
  1860. ) || 10)
  1861. ),
  1862. fillColor,
  1863. color;
  1864. fillColor =
  1865. (s.series.pointAttribs && s.series.pointAttribs().fill) ||
  1866. s.series.color;
  1867. if (options.colorByPoint) {
  1868. fillColor = s.series.chart.options.colors[si];
  1869. }
  1870. if (s.series.fillOpacity && options.fillOpacity) {
  1871. fillColor = new Color(fillColor).setOpacity(
  1872. pick(options.fillOpacity, 1.0)
  1873. ).get();
  1874. }
  1875. color = H.color(fillColor).rgba;
  1876. if (!settings.useAlpha) {
  1877. color[3] = 1.0;
  1878. }
  1879. // This is very much temporary
  1880. if (s.drawMode === 'lines' && settings.useAlpha && color[3] < 1) {
  1881. color[3] /= 10;
  1882. }
  1883. // Blending
  1884. if (options.boostBlending === 'add') {
  1885. gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  1886. gl.blendEquation(gl.FUNC_ADD);
  1887. } else if (options.boostBlending === 'mult') {
  1888. gl.blendFunc(gl.DST_COLOR, gl.ZERO);
  1889. } else if (options.boostBlending === 'darken') {
  1890. gl.blendFunc(gl.ONE, gl.ONE);
  1891. gl.blendEquation(gl.FUNC_MIN);
  1892. } else {
  1893. // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  1894. // gl.blendEquation(gl.FUNC_ADD);
  1895. gl.blendFuncSeparate(
  1896. gl.SRC_ALPHA,
  1897. gl.ONE_MINUS_SRC_ALPHA,
  1898. gl.ONE,
  1899. gl.ONE_MINUS_SRC_ALPHA
  1900. );
  1901. }
  1902. shader.reset();
  1903. // If there are entries in the colorData buffer, build and bind it.
  1904. if (s.colorData.length > 0) {
  1905. shader.setUniform('hasColor', 1.0);
  1906. cbuffer = GLVertexBuffer(gl, shader); // eslint-disable-line new-cap
  1907. cbuffer.build(s.colorData, 'aColor', 4);
  1908. cbuffer.bind();
  1909. }
  1910. // Set series specific uniforms
  1911. shader.setColor(color);
  1912. setXAxis(s.series.xAxis);
  1913. setYAxis(s.series.yAxis);
  1914. setThreshold(hasThreshold, translatedThreshold);
  1915. if (s.drawMode === 'points') {
  1916. if (options.marker && options.marker.radius) {
  1917. shader.setPointSize(options.marker.radius * 2.0);
  1918. } else {
  1919. shader.setPointSize(1);
  1920. }
  1921. }
  1922. // If set to true, the toPixels translations in the shader
  1923. // is skipped, i.e it's assumed that the value is a pixel coord.
  1924. shader.setSkipTranslation(s.skipTranslation);
  1925. if (s.series.type === 'bubble') {
  1926. shader.setBubbleUniforms(s.series, s.zMin, s.zMax);
  1927. }
  1928. shader.setDrawAsCircle(
  1929. (asCircle[s.series.type] && textureIsReady) || false
  1930. );
  1931. // Do the actual rendering
  1932. // If the line width is < 0, skip rendering of the lines. See #7833.
  1933. if (lineWidth > 0 || s.drawMode !== 'line_strip') {
  1934. for (sindex = 0; sindex < s.segments.length; sindex++) {
  1935. // if (s.segments[sindex].from < s.segments[sindex].to) {
  1936. vbuffer.render(
  1937. s.segments[sindex].from,
  1938. s.segments[sindex].to,
  1939. s.drawMode
  1940. );
  1941. // }
  1942. }
  1943. }
  1944. if (s.hasMarkers && showMarkers) {
  1945. if (options.marker && options.marker.radius) {
  1946. shader.setPointSize(options.marker.radius * 2.0);
  1947. } else {
  1948. shader.setPointSize(10);
  1949. }
  1950. shader.setDrawAsCircle(true);
  1951. for (sindex = 0; sindex < s.segments.length; sindex++) {
  1952. // if (s.segments[sindex].from < s.segments[sindex].to) {
  1953. vbuffer.render(
  1954. s.segments[sindex].from,
  1955. s.segments[sindex].to,
  1956. 'POINTS'
  1957. );
  1958. // }
  1959. }
  1960. }
  1961. });
  1962. if (settings.debug.timeRendering) {
  1963. console.timeEnd('gl rendering'); // eslint-disable-line no-console
  1964. }
  1965. if (postRenderCallback) {
  1966. postRenderCallback();
  1967. }
  1968. flush();
  1969. }
  1970. /*
  1971. * Render the data when ready
  1972. */
  1973. function renderWhenReady(chart) {
  1974. clear();
  1975. if (chart.renderer.forExport) {
  1976. return render(chart);
  1977. }
  1978. if (isInited) {
  1979. render(chart);
  1980. } else {
  1981. setTimeout(function () {
  1982. renderWhenReady(chart);
  1983. }, 1);
  1984. }
  1985. }
  1986. /*
  1987. * Set the viewport size in pixels
  1988. * Creates an orthographic perspective matrix and applies it.
  1989. * @param w {Integer} - the width of the viewport
  1990. * @param h {Integer} - the height of the viewport
  1991. */
  1992. function setSize(w, h) {
  1993. // Skip if there's no change
  1994. if (width === w && h === h) {
  1995. return;
  1996. }
  1997. width = w;
  1998. height = h;
  1999. shader.bind();
  2000. shader.setPMatrix(orthoMatrix(width, height));
  2001. }
  2002. /*
  2003. * Init OpenGL
  2004. * @param canvas {HTMLCanvas} - the canvas to render to
  2005. */
  2006. function init(canvas, noFlush) {
  2007. var i = 0,
  2008. contexts = [
  2009. 'webgl',
  2010. 'experimental-webgl',
  2011. 'moz-webgl',
  2012. 'webkit-3d'
  2013. ];
  2014. isInited = false;
  2015. if (!canvas) {
  2016. return false;
  2017. }
  2018. if (settings.debug.timeSetup) {
  2019. console.time('gl setup'); // eslint-disable-line no-console
  2020. }
  2021. for (; i < contexts.length; i++) {
  2022. gl = canvas.getContext(contexts[i], {
  2023. // premultipliedAlpha: false
  2024. });
  2025. if (gl) {
  2026. break;
  2027. }
  2028. }
  2029. if (gl) {
  2030. if (!noFlush) {
  2031. flush();
  2032. }
  2033. } else {
  2034. return false;
  2035. }
  2036. gl.enable(gl.BLEND);
  2037. // gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  2038. gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  2039. gl.disable(gl.DEPTH_TEST);
  2040. // gl.depthMask(gl.FALSE);
  2041. gl.depthFunc(gl.LESS);
  2042. shader = GLShader(gl); // eslint-disable-line new-cap
  2043. vbuffer = GLVertexBuffer(gl, shader); // eslint-disable-line new-cap
  2044. textureIsReady = false;
  2045. // Set up the circle texture used for bubbles
  2046. circleTextureHandle = gl.createTexture();
  2047. // Draw the circle
  2048. circleTexture.width = 512;
  2049. circleTexture.height = 512;
  2050. circleCtx.mozImageSmoothingEnabled = false;
  2051. circleCtx.webkitImageSmoothingEnabled = false;
  2052. circleCtx.msImageSmoothingEnabled = false;
  2053. circleCtx.imageSmoothingEnabled = false;
  2054. circleCtx.strokeStyle = 'rgba(255, 255, 255, 0)';
  2055. circleCtx.fillStyle = '#FFF';
  2056. circleCtx.beginPath();
  2057. circleCtx.arc(256, 256, 256, 0, 2 * Math.PI);
  2058. circleCtx.stroke();
  2059. circleCtx.fill();
  2060. try {
  2061. gl.bindTexture(gl.TEXTURE_2D, circleTextureHandle);
  2062. // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
  2063. gl.texImage2D(
  2064. gl.TEXTURE_2D,
  2065. 0,
  2066. gl.RGBA,
  2067. gl.RGBA,
  2068. gl.UNSIGNED_BYTE,
  2069. circleTexture
  2070. );
  2071. gl.texParameteri(
  2072. gl.TEXTURE_2D,
  2073. gl.TEXTURE_WRAP_S,
  2074. gl.CLAMP_TO_EDGE
  2075. );
  2076. gl.texParameteri(
  2077. gl.TEXTURE_2D,
  2078. gl.TEXTURE_WRAP_T,
  2079. gl.CLAMP_TO_EDGE
  2080. );
  2081. gl.texParameteri(
  2082. gl.TEXTURE_2D,
  2083. gl.TEXTURE_MAG_FILTER,
  2084. gl.LINEAR
  2085. );
  2086. gl.texParameteri(
  2087. gl.TEXTURE_2D,
  2088. gl.TEXTURE_MIN_FILTER,
  2089. gl.LINEAR
  2090. );
  2091. // gl.generateMipmap(gl.TEXTURE_2D);
  2092. gl.bindTexture(gl.TEXTURE_2D, null);
  2093. textureIsReady = true;
  2094. } catch (e) {}
  2095. isInited = true;
  2096. if (settings.debug.timeSetup) {
  2097. console.timeEnd('gl setup'); // eslint-disable-line no-console
  2098. }
  2099. return true;
  2100. }
  2101. /*
  2102. * Check if we have a valid OGL context
  2103. * @returns {Boolean} - true if the context is valid
  2104. */
  2105. function valid() {
  2106. return gl !== false;
  2107. }
  2108. /*
  2109. * Check if the renderer has been initialized
  2110. * @returns {Boolean} - true if it has, false if not
  2111. */
  2112. function inited() {
  2113. return isInited;
  2114. }
  2115. function destroy() {
  2116. flush();
  2117. vbuffer.destroy();
  2118. shader.destroy();
  2119. if (gl) {
  2120. if (circleTextureHandle) {
  2121. gl.deleteTexture(circleTextureHandle);
  2122. }
  2123. gl.canvas.width = 1;
  2124. gl.canvas.height = 1;
  2125. }
  2126. }
  2127. // /////////////////////////////////////////////////////////////////////////
  2128. exports = {
  2129. allocateBufferForSingleSeries: allocateBufferForSingleSeries,
  2130. pushSeries: pushSeries,
  2131. setSize: setSize,
  2132. inited: inited,
  2133. setThreshold: setThreshold,
  2134. init: init,
  2135. render: renderWhenReady,
  2136. settings: settings,
  2137. valid: valid,
  2138. clear: clear,
  2139. flush: flush,
  2140. setXAxis: setXAxis,
  2141. setYAxis: setYAxis,
  2142. data: data,
  2143. gl: getGL,
  2144. allocateBuffer: allocateBuffer,
  2145. destroy: destroy,
  2146. setOptions: setOptions
  2147. };
  2148. return exports;
  2149. }
  2150. // END OF WEBGL ABSTRACTIONS
  2151. // /////////////////////////////////////////////////////////////////////////////
  2152. /*
  2153. * Create a canvas + context and attach it to the target
  2154. * @param target {Highcharts.Chart|Highcharts.Series} - the canvas target
  2155. * @param chart {Highcharts.Chart} - the chart
  2156. */
  2157. function createAndAttachRenderer(chart, series) {
  2158. var width = chart.chartWidth,
  2159. height = chart.chartHeight,
  2160. target = chart,
  2161. targetGroup = chart.seriesGroup || series.group,
  2162. alpha = 1,
  2163. foSupported = doc.implementation.hasFeature(
  2164. 'www.http://w3.org/TR/SVG11/feature#Extensibility',
  2165. '1.1'
  2166. );
  2167. if (chart.isChartSeriesBoosting()) {
  2168. target = chart;
  2169. } else {
  2170. target = series;
  2171. }
  2172. // Support for foreignObject is flimsy as best.
  2173. // IE does not support it, and Chrome has a bug which messes up
  2174. // the canvas draw order.
  2175. // As such, we force the Image fallback for now, but leaving the
  2176. // actual Canvas path in-place in case this changes in the future.
  2177. foSupported = false;
  2178. if (!target.renderTarget) {
  2179. target.canvas = mainCanvas;
  2180. // Fall back to image tag if foreignObject isn't supported,
  2181. // or if we're exporting.
  2182. if (chart.renderer.forExport || !foSupported) {
  2183. target.renderTarget = chart.renderer.image(
  2184. '',
  2185. 0,
  2186. 0,
  2187. width,
  2188. height
  2189. )
  2190. .addClass('highcharts-boost-canvas')
  2191. .add(targetGroup);
  2192. target.boostClear = function () {
  2193. target.renderTarget.attr({ href: '' });
  2194. };
  2195. target.boostCopy = function () {
  2196. target.boostResizeTarget();
  2197. target.renderTarget.attr({
  2198. href: target.canvas.toDataURL('image/png')
  2199. });
  2200. };
  2201. } else {
  2202. target.renderTargetFo = chart.renderer
  2203. .createElement('foreignObject')
  2204. .add(targetGroup);
  2205. target.renderTarget = doc.createElement('canvas');
  2206. target.renderTargetCtx = target.renderTarget.getContext('2d');
  2207. target.renderTargetFo.element.appendChild(target.renderTarget);
  2208. target.boostClear = function () {
  2209. target.renderTarget.width = target.canvas.width;
  2210. target.renderTarget.height = target.canvas.height;
  2211. };
  2212. target.boostCopy = function () {
  2213. target.renderTarget.width = target.canvas.width;
  2214. target.renderTarget.height = target.canvas.height;
  2215. target.renderTargetCtx.drawImage(target.canvas, 0, 0);
  2216. };
  2217. }
  2218. target.boostResizeTarget = function () {
  2219. width = chart.chartWidth;
  2220. height = chart.chartHeight;
  2221. (target.renderTargetFo || target.renderTarget)
  2222. .attr({
  2223. x: 0,
  2224. y: 0,
  2225. width: width,
  2226. height: height
  2227. })
  2228. .css({
  2229. pointerEvents: 'none',
  2230. mixedBlendMode: 'normal',
  2231. opacity: alpha
  2232. });
  2233. if (target instanceof H.Chart) {
  2234. target.markerGroup.translate(
  2235. chart.plotLeft,
  2236. chart.plotTop
  2237. );
  2238. }
  2239. };
  2240. target.boostClipRect = chart.renderer.clipRect();
  2241. (target.renderTargetFo || target.renderTarget)
  2242. .clip(target.boostClipRect);
  2243. if (target instanceof H.Chart) {
  2244. target.markerGroup = target.renderer.g().add(targetGroup);
  2245. target.markerGroup.translate(series.xAxis.pos, series.yAxis.pos);
  2246. }
  2247. }
  2248. target.canvas.width = width;
  2249. target.canvas.height = height;
  2250. target.boostClipRect.attr(chart.getBoostClipRect(target));
  2251. target.boostResizeTarget();
  2252. target.boostClear();
  2253. if (!target.ogl) {
  2254. target.ogl = GLRenderer(function () { // eslint-disable-line new-cap
  2255. if (target.ogl.settings.debug.timeBufferCopy) {
  2256. console.time('buffer copy'); // eslint-disable-line no-console
  2257. }
  2258. target.boostCopy();
  2259. if (target.ogl.settings.debug.timeBufferCopy) {
  2260. console.timeEnd('buffer copy'); // eslint-disable-line no-console
  2261. }
  2262. }); // eslint-disable-line new-cap
  2263. target.ogl.init(target.canvas);
  2264. // target.ogl.clear();
  2265. target.ogl.setOptions(chart.options.boost || {});
  2266. if (target instanceof H.Chart) {
  2267. target.ogl.allocateBuffer(chart);
  2268. }
  2269. }
  2270. target.ogl.setSize(width, height);
  2271. return target.ogl;
  2272. }
  2273. /*
  2274. * Performs the actual render if the renderer is
  2275. * attached to the series.
  2276. * @param renderer {OGLRenderer} - the renderer
  2277. * @param series {Highcharts.Series} - the series
  2278. */
  2279. function renderIfNotSeriesBoosting(renderer, series, chart) {
  2280. if (renderer &&
  2281. series.renderTarget &&
  2282. series.canvas &&
  2283. !(chart || series.chart).isChartSeriesBoosting()
  2284. ) {
  2285. renderer.render(chart || series.chart);
  2286. }
  2287. }
  2288. function allocateIfNotSeriesBoosting(renderer, series) {
  2289. if (renderer &&
  2290. series.renderTarget &&
  2291. series.canvas &&
  2292. !series.chart.isChartSeriesBoosting()
  2293. ) {
  2294. renderer.allocateBufferForSingleSeries(series);
  2295. }
  2296. }
  2297. /*
  2298. * An "async" foreach loop. Uses a setTimeout to keep the loop from blocking the
  2299. * UI thread.
  2300. *
  2301. * @param arr {Array} - the array to loop through
  2302. * @param fn {Function} - the callback to call for each item
  2303. * @param finalFunc {Function} - the callback to call when done
  2304. * @param chunkSize {Number} - the number of iterations per timeout
  2305. * @param i {Number} - the current index
  2306. * @param noTimeout {Boolean} - set to true to skip timeouts
  2307. */
  2308. H.eachAsync = function (arr, fn, finalFunc, chunkSize, i, noTimeout) {
  2309. i = i || 0;
  2310. chunkSize = chunkSize || CHUNK_SIZE;
  2311. var threshold = i + chunkSize,
  2312. proceed = true;
  2313. while (proceed && i < threshold && i < arr.length) {
  2314. proceed = fn(arr[i], i);
  2315. ++i;
  2316. }
  2317. if (proceed) {
  2318. if (i < arr.length) {
  2319. if (noTimeout) {
  2320. H.eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout);
  2321. } else if (win.requestAnimationFrame) {
  2322. // If available, do requestAnimationFrame - shaves off a few ms
  2323. win.requestAnimationFrame(function () {
  2324. H.eachAsync(arr, fn, finalFunc, chunkSize, i);
  2325. });
  2326. } else {
  2327. setTimeout(function () {
  2328. H.eachAsync(arr, fn, finalFunc, chunkSize, i);
  2329. });
  2330. }
  2331. } else if (finalFunc) {
  2332. finalFunc();
  2333. }
  2334. }
  2335. };
  2336. // /////////////////////////////////////////////////////////////////////////////
  2337. // Following is the parts of the boost that's common between OGL/Legacy
  2338. /**
  2339. * Return a full Point object based on the index.
  2340. * The boost module uses stripped point objects for performance reasons.
  2341. * @param {Number} boostPoint A stripped-down point object
  2342. * @returns {Object} A Point object as per http://api.highcharts.com/highcharts#Point
  2343. */
  2344. Series.prototype.getPoint = function (boostPoint) {
  2345. var point = boostPoint,
  2346. xData = this.xData || this.options.xData || this.processedXData || false
  2347. ;
  2348. if (boostPoint && !(boostPoint instanceof this.pointClass)) {
  2349. point = (new this.pointClass()).init( // eslint-disable-line new-cap
  2350. this,
  2351. this.options.data[boostPoint.i],
  2352. xData ? xData[boostPoint.i] : undefined
  2353. );
  2354. point.category = point.x;
  2355. point.dist = boostPoint.dist;
  2356. point.distX = boostPoint.distX;
  2357. point.plotX = boostPoint.plotX;
  2358. point.plotY = boostPoint.plotY;
  2359. point.index = boostPoint.i;
  2360. }
  2361. return point;
  2362. };
  2363. /**
  2364. * Return a point instance from the k-d-tree
  2365. */
  2366. wrap(Series.prototype, 'searchPoint', function (proceed) {
  2367. return this.getPoint(
  2368. proceed.apply(this, [].slice.call(arguments, 1))
  2369. );
  2370. });
  2371. /**
  2372. * Extend series.destroy to also remove the fake k-d-tree points (#5137).
  2373. * Normally this is handled by Series.destroy that calls Point.destroy,
  2374. * but the fake search points are not registered like that.
  2375. */
  2376. addEvent(Series, 'destroy', function () {
  2377. var series = this,
  2378. chart = series.chart;
  2379. if (chart.markerGroup === series.markerGroup) {
  2380. series.markerGroup = null;
  2381. }
  2382. if (chart.hoverPoints) {
  2383. chart.hoverPoints = grep(chart.hoverPoints, function (point) {
  2384. return point.series === series;
  2385. });
  2386. }
  2387. if (chart.hoverPoint && chart.hoverPoint.series === series) {
  2388. chart.hoverPoint = null;
  2389. }
  2390. });
  2391. /**
  2392. * Do not compute extremes when min and max are set.
  2393. * If we use this in the core, we can add the hook
  2394. * to hasExtremes to the methods directly.
  2395. */
  2396. wrap(Series.prototype, 'getExtremes', function (proceed) {
  2397. if (!this.isSeriesBoosting || (!this.hasExtremes || !this.hasExtremes())) {
  2398. return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  2399. }
  2400. });
  2401. // Set default options
  2402. each(boostable,
  2403. function (type) {
  2404. if (plotOptions[type]) {
  2405. plotOptions[type].boostThreshold = 5000;
  2406. plotOptions[type].boostData = [];
  2407. seriesTypes[type].prototype.fillOpacity = true;
  2408. }
  2409. }
  2410. );
  2411. /**
  2412. * Override a bunch of methods the same way. If the number of points is
  2413. * below the threshold, run the original method. If not, check for a
  2414. * canvas version or do nothing.
  2415. *
  2416. * Note that we're not overriding any of these for heatmaps.
  2417. */
  2418. each([
  2419. 'translate',
  2420. 'generatePoints',
  2421. 'drawTracker',
  2422. 'drawPoints',
  2423. 'render'
  2424. ], function (method) {
  2425. function branch(proceed) {
  2426. var letItPass = this.options.stacking &&
  2427. (method === 'translate' || method === 'generatePoints'),
  2428. enabled = pick(
  2429. (
  2430. this.chart &&
  2431. this.chart.options &&
  2432. this.chart.options.boost &&
  2433. this.chart.options.boost.enabled
  2434. ),
  2435. true
  2436. );
  2437. if (
  2438. !this.isSeriesBoosting ||
  2439. letItPass ||
  2440. !enabled ||
  2441. this.type === 'heatmap' ||
  2442. this.type === 'treemap' ||
  2443. !boostableMap[this.type]
  2444. ) {
  2445. proceed.call(this);
  2446. // If a canvas version of the method exists, like renderCanvas(), run
  2447. } else if (this[method + 'Canvas']) {
  2448. this[method + 'Canvas']();
  2449. }
  2450. }
  2451. wrap(Series.prototype, method, branch);
  2452. // A special case for some types - their translate method is already wrapped
  2453. if (method === 'translate') {
  2454. each(
  2455. ['column', 'bar', 'arearange', 'columnrange', 'heatmap', 'treemap'],
  2456. function (type) {
  2457. if (seriesTypes[type]) {
  2458. wrap(seriesTypes[type].prototype, method, branch);
  2459. }
  2460. }
  2461. );
  2462. }
  2463. });
  2464. /** If the series is a heatmap or treemap, or if the series is not boosting
  2465. * do the default behaviour. Otherwise, process if the series has no
  2466. * extremes.
  2467. */
  2468. wrap(Series.prototype, 'processData', function (proceed) {
  2469. var series = this,
  2470. dataToMeasure = this.options.data;
  2471. // Used twice in this function, first on this.options.data, the second
  2472. // time it runs the check again after processedXData is built.
  2473. // @todo Check what happens with data grouping
  2474. function getSeriesBoosting(data) {
  2475. return series.chart.isChartSeriesBoosting() || (
  2476. (data ? data.length : 0) >=
  2477. (series.options.boostThreshold || Number.MAX_VALUE)
  2478. );
  2479. }
  2480. if (boostableMap[this.type]) {
  2481. // If there are no extremes given in the options, we also need to
  2482. // process the data to read the data extremes. If this is a heatmap, do
  2483. // default behaviour.
  2484. if (
  2485. !getSeriesBoosting(dataToMeasure) || // First pass with options.data
  2486. this.type === 'heatmap' ||
  2487. this.type === 'treemap' ||
  2488. this.options.stacking || // processedYData for the stack (#7481)
  2489. !this.hasExtremes ||
  2490. !this.hasExtremes(true)
  2491. ) {
  2492. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  2493. dataToMeasure = this.processedXData;
  2494. }
  2495. // Set the isBoosting flag, second pass with processedXData to see if we
  2496. // have zoomed.
  2497. this.isSeriesBoosting = getSeriesBoosting(dataToMeasure);
  2498. // Enter or exit boost mode
  2499. if (this.isSeriesBoosting) {
  2500. this.enterBoost();
  2501. } else if (this.exitBoost) {
  2502. this.exitBoost();
  2503. }
  2504. // The series type is not boostable
  2505. } else {
  2506. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  2507. }
  2508. });
  2509. addEvent(Series, 'hide', function () {
  2510. if (this.canvas && this.renderTarget) {
  2511. if (this.ogl) {
  2512. this.ogl.clear();
  2513. }
  2514. this.boostClear();
  2515. }
  2516. });
  2517. /**
  2518. * Enter boost mode and apply boost-specific properties.
  2519. */
  2520. Series.prototype.enterBoost = function () {
  2521. this.alteredByBoost = [];
  2522. // Save the original values, including whether it was an own property or
  2523. // inherited from the prototype.
  2524. each(['allowDG', 'directTouch', 'stickyTracking'], function (prop) {
  2525. this.alteredByBoost.push({
  2526. prop: prop,
  2527. val: this[prop],
  2528. own: this.hasOwnProperty(prop)
  2529. });
  2530. }, this);
  2531. this.allowDG = false;
  2532. this.directTouch = false;
  2533. this.stickyTracking = true;
  2534. // Once we've been in boost mode, we don't want animation when returning to
  2535. // vanilla mode.
  2536. this.animate = null;
  2537. // Hide series label if any
  2538. if (this.labelBySeries) {
  2539. this.labelBySeries = this.labelBySeries.destroy();
  2540. }
  2541. };
  2542. /**
  2543. * Exit from boost mode and restore non-boost properties.
  2544. */
  2545. Series.prototype.exitBoost = function () {
  2546. // Reset instance properties and/or delete instance properties and go back
  2547. // to prototype
  2548. each(this.alteredByBoost || [], function (setting) {
  2549. if (setting.own) {
  2550. this[setting.prop] = setting.val;
  2551. } else {
  2552. // Revert to prototype
  2553. delete this[setting.prop];
  2554. }
  2555. }, this);
  2556. // Clear previous run
  2557. if (this.boostClear) {
  2558. this.boostClear();
  2559. }
  2560. };
  2561. Series.prototype.hasExtremes = function (checkX) {
  2562. var options = this.options,
  2563. data = options.data,
  2564. xAxis = this.xAxis && this.xAxis.options,
  2565. yAxis = this.yAxis && this.yAxis.options;
  2566. return data.length > (options.boostThreshold || Number.MAX_VALUE) &&
  2567. isNumber(yAxis.min) && isNumber(yAxis.max) &&
  2568. (!checkX || (isNumber(xAxis.min) && isNumber(xAxis.max)));
  2569. };
  2570. /**
  2571. * If implemented in the core, parts of this can probably be
  2572. * shared with other similar methods in Highcharts.
  2573. */
  2574. Series.prototype.destroyGraphics = function () {
  2575. var series = this,
  2576. points = this.points,
  2577. point,
  2578. i;
  2579. if (points) {
  2580. for (i = 0; i < points.length; i = i + 1) {
  2581. point = points[i];
  2582. if (point && point.destroyElements) {
  2583. point.destroyElements(); // #7557
  2584. }
  2585. }
  2586. }
  2587. each(['graph', 'area', 'tracker'], function (prop) {
  2588. if (series[prop]) {
  2589. series[prop] = series[prop].destroy();
  2590. }
  2591. });
  2592. };
  2593. /*
  2594. * Returns true if the current browser supports webgl
  2595. */
  2596. H.hasWebGLSupport = function () {
  2597. var i = 0,
  2598. canvas,
  2599. contexts = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'],
  2600. context = false;
  2601. if (typeof win.WebGLRenderingContext !== 'undefined') {
  2602. canvas = doc.createElement('canvas');
  2603. for (; i < contexts.length; i++) {
  2604. try {
  2605. context = canvas.getContext(contexts[i]);
  2606. if (typeof context !== 'undefined' && context !== null) {
  2607. return true;
  2608. }
  2609. } catch (e) {
  2610. }
  2611. }
  2612. }
  2613. return false;
  2614. };
  2615. /* Used for treemap|heatmap.drawPoints */
  2616. function pointDrawHandler(proceed) {
  2617. var enabled = true,
  2618. renderer;
  2619. if (this.chart.options && this.chart.options.boost) {
  2620. enabled = typeof this.chart.options.boost.enabled === 'undefined' ?
  2621. true :
  2622. this.chart.options.boost.enabled;
  2623. }
  2624. if (!enabled || !this.isSeriesBoosting) {
  2625. return proceed.call(this);
  2626. }
  2627. this.chart.isBoosting = true;
  2628. // Make sure we have a valid OGL context
  2629. renderer = createAndAttachRenderer(this.chart, this);
  2630. if (renderer) {
  2631. allocateIfNotSeriesBoosting(renderer, this);
  2632. renderer.pushSeries(this);
  2633. }
  2634. renderIfNotSeriesBoosting(renderer, this);
  2635. }
  2636. // /////////////////////////////////////////////////////////////////////////////
  2637. // We're wrapped in a closure, so just return if there's no webgl support
  2638. if (!H.hasWebGLSupport()) {
  2639. if (typeof H.initCanvasBoost !== 'undefined') {
  2640. // Fallback to canvas boost
  2641. H.initCanvasBoost();
  2642. } else {
  2643. H.error(26);
  2644. }
  2645. } else {
  2646. // /////////////////////////////////////////////////////////////////////////
  2647. // GL-SPECIFIC WRAPPINGS FOLLOWS
  2648. H.extend(Series.prototype, {
  2649. renderCanvas: function () {
  2650. var series = this,
  2651. options = series.options || {},
  2652. renderer = false,
  2653. chart = series.chart,
  2654. xAxis = this.xAxis,
  2655. yAxis = this.yAxis,
  2656. xData = options.xData || series.processedXData,
  2657. yData = options.yData || series.processedYData,
  2658. rawData = options.data,
  2659. xExtremes = xAxis.getExtremes(),
  2660. xMin = xExtremes.min,
  2661. xMax = xExtremes.max,
  2662. yExtremes = yAxis.getExtremes(),
  2663. yMin = yExtremes.min,
  2664. yMax = yExtremes.max,
  2665. pointTaken = {},
  2666. lastClientX,
  2667. sampling = !!series.sampling,
  2668. points,
  2669. enableMouseTracking = options.enableMouseTracking !== false,
  2670. threshold = options.threshold,
  2671. yBottom = yAxis.getThreshold(threshold),
  2672. isRange = series.pointArrayMap &&
  2673. series.pointArrayMap.join(',') === 'low,high',
  2674. isStacked = !!options.stacking,
  2675. cropStart = series.cropStart || 0,
  2676. requireSorting = series.requireSorting,
  2677. useRaw = !xData,
  2678. minVal,
  2679. maxVal,
  2680. minI,
  2681. maxI,
  2682. boostOptions,
  2683. xDataFull = (
  2684. this.xData ||
  2685. this.options.xData ||
  2686. this.processedXData ||
  2687. false
  2688. ),
  2689. addKDPoint = function (clientX, plotY, i) {
  2690. // Shaves off about 60ms compared to repeated concatination
  2691. index = clientX + ',' + plotY;
  2692. // The k-d tree requires series points.
  2693. // Reduce the amount of points, since the time to build the
  2694. // tree increases exponentially.
  2695. if (enableMouseTracking && !pointTaken[index]) {
  2696. pointTaken[index] = true;
  2697. if (chart.inverted) {
  2698. clientX = xAxis.len - clientX;
  2699. plotY = yAxis.len - plotY;
  2700. }
  2701. points.push({
  2702. x: xDataFull ? xDataFull[cropStart + i] : false,
  2703. clientX: clientX,
  2704. plotX: clientX,
  2705. plotY: plotY,
  2706. i: cropStart + i
  2707. });
  2708. }
  2709. };
  2710. // Get or create the renderer
  2711. renderer = createAndAttachRenderer(chart, series);
  2712. chart.isBoosting = true;
  2713. boostOptions = renderer.settings;
  2714. if (!this.visible) {
  2715. return;
  2716. }
  2717. // If we are zooming out from SVG mode, destroy the graphics
  2718. if (this.points || this.graph) {
  2719. this.animate = null;
  2720. this.destroyGraphics();
  2721. }
  2722. // If we're rendering per. series we should create the marker groups
  2723. // as usual.
  2724. if (!chart.isChartSeriesBoosting()) {
  2725. this.markerGroup = series.plotGroup(
  2726. 'markerGroup',
  2727. 'markers',
  2728. true,
  2729. 1,
  2730. chart.seriesGroup
  2731. );
  2732. } else {
  2733. // Use a single group for the markers
  2734. this.markerGroup = chart.markerGroup;
  2735. // When switching from chart boosting mode, destroy redundant
  2736. // series boosting targets
  2737. if (this.renderTarget) {
  2738. this.renderTarget = this.renderTarget.destroy();
  2739. }
  2740. }
  2741. points = this.points = [];
  2742. // Do not start building while drawing
  2743. series.buildKDTree = noop;
  2744. if (renderer) {
  2745. allocateIfNotSeriesBoosting(renderer, this);
  2746. renderer.pushSeries(series);
  2747. // Perform the actual renderer if we're on series level
  2748. renderIfNotSeriesBoosting(renderer, this, chart);
  2749. // console.log(series, chart);
  2750. }
  2751. /* This builds the KD-tree */
  2752. function processPoint(d, i) {
  2753. var x,
  2754. y,
  2755. clientX,
  2756. plotY,
  2757. isNull,
  2758. low = false,
  2759. chartDestroyed = typeof chart.index === 'undefined',
  2760. isYInside = true;
  2761. if (!chartDestroyed) {
  2762. if (useRaw) {
  2763. x = d[0];
  2764. y = d[1];
  2765. } else {
  2766. x = d;
  2767. y = yData[i];
  2768. }
  2769. // Resolve low and high for range series
  2770. if (isRange) {
  2771. if (useRaw) {
  2772. y = d.slice(1, 3);
  2773. }
  2774. low = y[0];
  2775. y = y[1];
  2776. } else if (isStacked) {
  2777. x = d.x;
  2778. y = d.stackY;
  2779. low = y - d.y;
  2780. }
  2781. isNull = y === null;
  2782. // Optimize for scatter zooming
  2783. if (!requireSorting) {
  2784. isYInside = y >= yMin && y <= yMax;
  2785. }
  2786. if (!isNull && x >= xMin && x <= xMax && isYInside) {
  2787. // We use ceil to allow the KD tree to work with sub
  2788. // pixels, which can be used in boost to space pixels
  2789. clientX = Math.ceil(xAxis.toPixels(x, true));
  2790. if (sampling) {
  2791. if (minI === undefined || clientX === lastClientX) {
  2792. if (!isRange) {
  2793. low = y;
  2794. }
  2795. if (maxI === undefined || y > maxVal) {
  2796. maxVal = y;
  2797. maxI = i;
  2798. }
  2799. if (minI === undefined || low < minVal) {
  2800. minVal = low;
  2801. minI = i;
  2802. }
  2803. }
  2804. // Add points and reset
  2805. if (clientX !== lastClientX) {
  2806. if (minI !== undefined) { // maxI is number too
  2807. plotY = yAxis.toPixels(maxVal, true);
  2808. yBottom = yAxis.toPixels(minVal, true);
  2809. addKDPoint(clientX, plotY, maxI);
  2810. if (yBottom !== plotY) {
  2811. addKDPoint(clientX, yBottom, minI);
  2812. }
  2813. }
  2814. minI = maxI = undefined;
  2815. lastClientX = clientX;
  2816. }
  2817. } else {
  2818. plotY = Math.ceil(yAxis.toPixels(y, true));
  2819. addKDPoint(clientX, plotY, i);
  2820. }
  2821. }
  2822. }
  2823. return !chartDestroyed;
  2824. }
  2825. function doneProcessing() {
  2826. fireEvent(series, 'renderedCanvas');
  2827. // Go back to prototype, ready to build
  2828. delete series.buildKDTree;
  2829. series.buildKDTree();
  2830. if (boostOptions.debug.timeKDTree) {
  2831. console.timeEnd('kd tree building'); // eslint-disable-line no-console
  2832. }
  2833. }
  2834. // Loop over the points to build the k-d tree - skip this if
  2835. // exporting
  2836. if (!chart.renderer.forExport) {
  2837. if (boostOptions.debug.timeKDTree) {
  2838. console.time('kd tree building'); // eslint-disable-line no-console
  2839. }
  2840. H.eachAsync(
  2841. isStacked ? series.data : (xData || rawData),
  2842. processPoint,
  2843. doneProcessing
  2844. );
  2845. }
  2846. }
  2847. });
  2848. /*
  2849. * We need to handle heatmaps separatly, since we can't perform the
  2850. * size/color calculations in the shader easily.
  2851. *
  2852. * This likely needs future optimization.
  2853. *
  2854. */
  2855. each(['heatmap', 'treemap'],
  2856. function (t) {
  2857. if (seriesTypes[t]) {
  2858. wrap(seriesTypes[t].prototype, 'drawPoints', pointDrawHandler);
  2859. }
  2860. }
  2861. );
  2862. if (seriesTypes.bubble) {
  2863. // By default, the bubble series does not use the KD-tree, so force it
  2864. // to.
  2865. delete seriesTypes.bubble.prototype.buildKDTree;
  2866. // seriesTypes.bubble.prototype.directTouch = false;
  2867. // Needed for markers to work correctly
  2868. wrap(
  2869. seriesTypes.bubble.prototype,
  2870. 'markerAttribs',
  2871. function (proceed) {
  2872. if (this.isSeriesBoosting) {
  2873. return false;
  2874. }
  2875. return proceed.apply(this, [].slice.call(arguments, 1));
  2876. }
  2877. );
  2878. }
  2879. seriesTypes.scatter.prototype.fill = true;
  2880. extend(seriesTypes.area.prototype, {
  2881. fill: true,
  2882. fillOpacity: true,
  2883. sampling: true
  2884. });
  2885. extend(seriesTypes.column.prototype, {
  2886. fill: true,
  2887. sampling: true
  2888. });
  2889. /**
  2890. * Take care of the canvas blitting
  2891. */
  2892. H.Chart.prototype.callbacks.push(function (chart) {
  2893. /* Convert chart-level canvas to image */
  2894. function canvasToSVG() {
  2895. if (chart.ogl && chart.isChartSeriesBoosting()) {
  2896. chart.ogl.render(chart);
  2897. }
  2898. }
  2899. /* Clear chart-level canvas */
  2900. function preRender() {
  2901. // Reset force state
  2902. chart.boostForceChartBoost = undefined;
  2903. chart.boostForceChartBoost = shouldForceChartSeriesBoosting(chart);
  2904. chart.isBoosting = false;
  2905. if (!chart.isChartSeriesBoosting() && chart.didBoost) {
  2906. chart.didBoost = false;
  2907. }
  2908. // Clear the canvas
  2909. if (chart.boostClear) {
  2910. chart.boostClear();
  2911. }
  2912. if (chart.canvas && chart.ogl && chart.isChartSeriesBoosting()) {
  2913. chart.didBoost = true;
  2914. // Allocate
  2915. chart.ogl.allocateBuffer(chart);
  2916. }
  2917. // see #6518 + #6739
  2918. if (
  2919. chart.markerGroup &&
  2920. chart.xAxis &&
  2921. chart.xAxis.length > 0 &&
  2922. chart.yAxis &&
  2923. chart.yAxis.length > 0
  2924. ) {
  2925. chart.markerGroup.translate(
  2926. chart.xAxis[0].pos,
  2927. chart.yAxis[0].pos
  2928. );
  2929. }
  2930. }
  2931. addEvent(chart, 'predraw', preRender);
  2932. addEvent(chart, 'render', canvasToSVG);
  2933. // addEvent(chart, 'zoom', function () {
  2934. // chart.boostForceChartBoost =
  2935. // shouldForceChartSeriesBoosting(chart);
  2936. // });
  2937. });
  2938. } // if hasCanvasSupport
  2939. }(Highcharts));
  2940. }));