boost.src.js 108 KB

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