boost.src.js 114 KB

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